From 5a9ddad7a61f0b443b556deaf7f5fcf513ff785c Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Thu, 22 Jan 2026 21:56:03 +0000 Subject: [PATCH] Migrate remaining SEPs missed in PR #1804 Add 10 SEPs that were missed during the initial migration in PR #1804: - SEP-991: OAuth Client ID Metadata Documents (Final) - SEP-1024: MCP Client Security Requirements (Final - PR #1025 merged) - SEP-1034: Default values for elicitation schemas (Final) - SEP-1036: URL Mode Elicitation (Final) - SEP-1303: Input Validation Errors (Final) - SEP-1577: Sampling With Tools (Final) - SEP-1613: JSON Schema 2020-12 Default Dialect (Final) - SEP-1686: Tasks (Final) - SEP-1699: SSE Polling via server-side disconnect (Final) - SEP-1730: SDKs Tiering System (Final) Note: SEP-1309 (Specification Version Management) was NOT included as PR #1404 implementing it is still open. Also fixes formatting issues in SEP metadata (Type, Author fields). --- ...security-requirements-for-local-server-.md | 104 ++ ...fault-values-for-all-primitive-types-in.md | 143 +++ ...icitation-for-secure-out-of-band-intera.md | 304 +++++ ...idation-errors-as-tool-execution-errors.md | 178 +++ seps/1577--sampling-with-tools.md | 400 ++++++ ...son-schema-2020-12-as-default-dialect-f.md | 171 +++ seps/1686-tasks.md | 1083 +++++++++++++++++ ...-sse-polling-via-server-side-disconnect.md | 44 + seps/1730-sdks-tiering-system.md | 247 ++++ ...based-client-registration-using-oauth-c.md | 278 +++++ 10 files changed, 2952 insertions(+) create mode 100644 seps/1024-mcp-client-security-requirements-for-local-server-.md create mode 100644 seps/1034--support-default-values-for-all-primitive-types-in.md create mode 100644 seps/1036-url-mode-elicitation-for-secure-out-of-band-intera.md create mode 100644 seps/1303-input-validation-errors-as-tool-execution-errors.md create mode 100644 seps/1577--sampling-with-tools.md create mode 100644 seps/1613-establish-json-schema-2020-12-as-default-dialect-f.md create mode 100644 seps/1686-tasks.md create mode 100644 seps/1699-support-sse-polling-via-server-side-disconnect.md create mode 100644 seps/1730-sdks-tiering-system.md create mode 100644 seps/991-enable-url-based-client-registration-using-oauth-c.md diff --git a/seps/1024-mcp-client-security-requirements-for-local-server-.md b/seps/1024-mcp-client-security-requirements-for-local-server-.md new file mode 100644 index 000000000..2a3b2b1d2 --- /dev/null +++ b/seps/1024-mcp-client-security-requirements-for-local-server-.md @@ -0,0 +1,104 @@ +# SEP-1024: MCP Client Security Requirements for Local Server Installation + +- **Status**: Final +- **Type**: Standards Track +- **Created**: 2025-07-22 +- **Author(s)**: Den Delimarsky +- **Issue**: #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 diff --git a/seps/1034--support-default-values-for-all-primitive-types-in.md b/seps/1034--support-default-values-for-all-primitive-types-in.md new file mode 100644 index 000000000..a0a3c9492 --- /dev/null +++ b/seps/1034--support-default-values-for-all-primitive-types-in.md @@ -0,0 +1,143 @@ +# SEP-1034: 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.com) +- **Issue**: #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 +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 +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 +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 +- A demo with the above email reply workflow: https://asciinema.org/a/X7aQZjT2B5jVwn9dJ9sqQVkOM + +## Specification + +### Schema Changes + +Extend the elicitation primitive schemas to include optional default values: + +```typescript +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 diff --git a/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera.md b/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera.md new file mode 100644 index 000000000..9eb50ceb6 --- /dev/null +++ b/seps/1036-url-mode-elicitation-for-secure-out-of-band-intera.md @@ -0,0 +1,304 @@ +# SEP-1036: URL Mode Elicitation for secure out-of-band interactions + +- **Status**: Final +- **Type**: Standards Track +- **Created**: 2025-07-22 +- **Author(s)**: Nate Barbettini (@nbarbettini) and Wils Dawson (@wdawson) +- **Issue**: #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 +{ + "capabilities": { + "elicitation": { + "form": {}, + "url": {} + } + } +} +``` + +For backwards compatibility, an empty capabilities object is equivalent to declaring support for `form` mode only: + +```jsonc +{ + "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 +{ + "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 +{ + "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 +{ + "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 +{ + "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 +{ + "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 + { + "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 + +## 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 diff --git a/seps/1303-input-validation-errors-as-tool-execution-errors.md b/seps/1303-input-validation-errors-as-tool-execution-errors.md new file mode 100644 index 000000000..93f9c0200 --- /dev/null +++ b/seps/1303-input-validation-errors-as-tool-execution-errors.md @@ -0,0 +1,178 @@ +# SEP-1303: Input Validation Errors as Tool Execution Errors + +- **Status**: Final +- **Type**: Standards Track +- **Created**: 2025-08-05 +- **Author(s)**: @fredericbarthelet +- **Issue**: #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 +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 +// 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 +// 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 diff --git a/seps/1577--sampling-with-tools.md b/seps/1577--sampling-with-tools.md new file mode 100644 index 000000000..6390a7eba --- /dev/null +++ b/seps/1577--sampling-with-tools.md @@ -0,0 +1,400 @@ +# SEP-1577: Sampling With Tools + +- **Status**: Final +- **Type**: Standards Track +- **Created**: 2025-09-30 +- **Author(s)**: Olivier Chafik (@ochafik) +- **Issue**: #1577 + +| SEP Number | #1577 | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| **Title** | Sampling With Tools | +| **Author** | Olivier Chafik | +| **Sponsor** | @bhosmer-ant | +| **Status** | Draft | +| **Created** | 2025-09-29 | +| **Specification** | MCP 2025-06-18 | +| **Prototype** | https://github.com/modelcontextprotocol/typescript-sdk/pull/991 | +| **PR** | https://github.com/modelcontextprotocol/modelcontextprotocol/pull/1796 | +| **SDKs** | https://github.com/modelcontextprotocol/python-sdk/pull/1594 https://github.com/modelcontextprotocol/typescript-sdk/pull/1101 | + +**Updates**: + +- _Oct 1_: renamed `tool_choice` -> `toolChoice` (+ `"none"` value); removed exotic `stopReason`s `"refusal" & "other"`; allowed `{CreateMessageResult,SamplingMessage}.content` to be single contents or arrays of contents; +- _Oct 6_: aligned `ToolResultContent` on `CallToolResult` (support image / audio); added "Possible Follow Ups" section. +- _Oct 10_: updated reference impl example w/ simple tool registry (unify mcp tools w/ tool loop tools, see [comment below](https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1577#issuecomment-3389273471)) and a "choose your own adventure" game that uses sampling w/ tools + elicitation. +- _Oct 27_: aligned `ToolResultContent.content` on `CallToolResult.content` (using [ContentBlock](https://modelcontextprotocol.io/specification/2025-06-18/schema#contentblock)); added `ToolResultContent._meta` +- _Nov 5_: + - kept `stopReason` as open string but w/ redundant explicit enums for visibility + - removed requirement to throw when `includeContext` not matching advertised `ClientCapabilities.sampling.context` + - mitigates backwards compatibility issue of `CreateMessageResult.content` being an array of contents OR a single content by saying sampling _MUST NOT_ return an array in earlier spec versions (+ ackowledging SDK updates of code w/ sampling will need small code changes) +- _Nov 7_: renamed type `ToolCallContent` to `ToolUseContent` (to match its `tool_use` type & the `toolUse` `stopReason`). SEP was approved! +- _Nov 10_: removing `disable_parallel_tool_use` / keeping for a later update as the Gemini API has no way to implement this for now. +- _Nov 11_: added extra notes about Gemini API's function calling modes & roles; requiring SamplingMessage w/ tool result contents not be mixed w/ other content types + +## 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 + 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 + 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 + /* + 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 + [ + { 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 + [ + { + "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 + /* + 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 + +Theses 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 + 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 +{ + 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 +interface CreateMessageRequest { +  method: “sampling/createMessage”; +  params: { +    messages: SamplingMessage[]; + ... + format: { + type: "json_schema", + "schema": { + "type": "array", + "minItems": 5, + "maxItems": 100 + } + } + } +``` diff --git a/seps/1613-establish-json-schema-2020-12-as-default-dialect-f.md b/seps/1613-establish-json-schema-2020-12-as-default-dialect-f.md new file mode 100644 index 000000000..cc96b7e72 --- /dev/null +++ b/seps/1613-establish-json-schema-2020-12-as-default-dialect-f.md @@ -0,0 +1,171 @@ +# SEP-1613: Establish JSON Schema 2020-12 as Default Dialect for MCP + +- **Status**: Final +- **Type**: Standards Track +- **Created**: 2025-10-06 +- **Author(s)**: Ola Hungerford +- **Issue**: #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 +{ + "$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 +{ + "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. diff --git a/seps/1686-tasks.md b/seps/1686-tasks.md new file mode 100644 index 000000000..62bd93283 --- /dev/null +++ b/seps/1686-tasks.md @@ -0,0 +1,1083 @@ +# SEP-1686: Tasks + +- **Status**: Final +- **Type**: Standards Track +- **Created**: 2025-10-20 +- **Author(s)**: Surbhi Bansal, Luca Chang +- **Issue**: #1686 + +## Abstract + +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 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 +{ + "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 +{ + "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 +{ + "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 +{ + "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 +{ + "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 +{ + "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 +{ + "jsonrpc": "2.0", + "id": 5, + "method": "tasks/list", + "params": { + "cursor": "optional-cursor-value" + } +} +``` + +**Response:** + +```json +{ + "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 +{ + "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 +{ + "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 +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. +1. If a receiver accepts a delete request, it **SHOULD** delete the task and all associated results and metadata. +1. Receivers **MAY** choose not to support deletion at all, or only support deletion for tasks in certain statuses (e.g., only terminal statuses). +1. 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 + +### 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 +{ + "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 +{ + "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 +{ + taskId: string; // The task identifier to query +} +``` + +#### Task Get Response + +The `tasks/get` response includes: + +```typescript +{ + 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 +{ + 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 +{ + // 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 +{ + cursor?: string; // Optional cursor for pagination +} +``` + +#### Task List Response + +The `tasks/list` response includes: + +```typescript +{ + 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 +{ + "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 +{ + "jsonrpc": "2.0", + "id": 70, + "error": { + "code": -32602, + "message": "Failed to retrieve task: Task not found" + } +} +``` + +**Example: Task expired** + +```json +{ + "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 +{ + "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 +{ + "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 +{ + taskId: string; + status: "failed"; + keepAlive: number | null; + pollFrequency?: number; + error?: string; // Description of what went wrong +} +``` + +**Example: Task with execution error** + +```json +{ + "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 +// === 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 +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 +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 +{ + 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. + +
diff --git a/seps/1699-support-sse-polling-via-server-side-disconnect.md b/seps/1699-support-sse-polling-via-server-side-disconnect.md new file mode 100644 index 000000000..80eb23060 --- /dev/null +++ b/seps/1699-support-sse-polling-via-server-side-disconnect.md @@ -0,0 +1,44 @@ +# SEP-1699: Support SSE polling via server-side disconnect + +- **Status**: Final +- **Type**: Standards Track +- **Created**: 2025-10-22 +- **Author(s)**: Jonathan Hefner (@jonathanhefner) +- **Issue**: #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). diff --git a/seps/1730-sdks-tiering-system.md b/seps/1730-sdks-tiering-system.md new file mode 100644 index 000000000..fe3112567 --- /dev/null +++ b/seps/1730-sdks-tiering-system.md @@ -0,0 +1,247 @@ +# SEP-1730: SDKs Tiering System + +- **Status**: Final +- **Type**: Standards Track +- **Created**: 2025-10-29 +- **Author(s)**: Inna Harper, Felix Weinberger +- **Issue**: #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 +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. diff --git a/seps/991-enable-url-based-client-registration-using-oauth-c.md b/seps/991-enable-url-based-client-registration-using-oauth-c.md new file mode 100644 index 000000000..7c8ea2912 --- /dev/null +++ b/seps/991-enable-url-based-client-registration-using-oauth-c.md @@ -0,0 +1,278 @@ +# SEP-991: 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) Aaron Parecki (@aaronpk) +- **Issue**: #991 + +# SEP: OAuth Client ID Metadata Documents for MCP + +## 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 + 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 +{ + "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 motiviation behind this proposal. + +#### Risk: Client implementation burden, espcially 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/)