Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/draft-protocol-version-opt-in.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@modelcontextprotocol/core': patch
'@modelcontextprotocol/client': patch
'@modelcontextprotocol/server': patch
---

Adds the `DRAFT_PROTOCOL_VERSION_2026` / `DRAFT_PROTOCOL_VERSIONS` constants and the `allowDraftVersions` option. Draft protocol versions can now be listed in `supportedProtocolVersions` when explicitly allowed; they are never negotiable by default and never appear in the default supported set.

`supportedProtocolVersions` entries are now validated at construction: every entry must be a released protocol version (`SUPPORTED_PROTOCOL_VERSIONS`) or a known draft version (`DRAFT_PROTOCOL_VERSIONS`), and listing a draft version without `allowDraftVersions: true` throws.
6 changes: 6 additions & 0 deletions docs/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ const systemPrompt = ['You are a helpful assistant.', instructions].filter(Boole
console.log(systemPrompt);
```

### Protocol versions

During initialization the client requests the first entry of its supported version list and accepts whichever entry of that list the server responds with — by default the released versions in {@linkcode @modelcontextprotocol/client!index.SUPPORTED_PROTOCOL_VERSIONS | SUPPORTED_PROTOCOL_VERSIONS}. Pass `supportedProtocolVersions` in the client options to restrict or reorder that list; every entry must be a released version or a known draft version, otherwise the constructor throws.

Draft (unreleased) protocol revisions, listed in {@linkcode @modelcontextprotocol/client!index.DRAFT_PROTOCOL_VERSIONS | DRAFT_PROTOCOL_VERSIONS}, never appear in the default set and require a two-key opt-in: list the draft version in `supportedProtocolVersions` **and** set `allowDraftVersions: true`. With only one of the two keys, construction throws. The opt-in only makes the configuration constructible — the SDK does not yet negotiate draft protocol versions, so an opted-in client still connects with the released protocol.

## Authentication

MCP servers can require authentication before accepting client connections (see [Authorization](https://modelcontextprotocol.io/specification/latest/basic/authorization) in the MCP specification). Pass an {@linkcode @modelcontextprotocol/client!client/auth.AuthProvider | AuthProvider} to {@linkcode @modelcontextprotocol/client!client/streamableHttp.StreamableHTTPClientTransport | StreamableHTTPClientTransport}. The transport calls `token()` before every request and `onUnauthorized()` (if provided) on 401, then retries once.
Expand Down
9 changes: 8 additions & 1 deletion docs/migration-SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -501,10 +501,17 @@ Type changes in handler context:

> These task APIs are `@experimental` and may change without notice.

## 13. Client Behavioral Changes
## 13. Client / Server Behavioral Changes

`Client.listPrompts()`, `listResources()`, `listResourceTemplates()`, `listTools()` now return empty results when the server lacks the corresponding capability (instead of sending the request). Set `enforceStrictCapabilities: true` in `ClientOptions` to throw an error instead.

`supportedProtocolVersions` (in `ClientOptions`/`ServerOptions`) is validated at construction. Every entry must be in `SUPPORTED_PROTOCOL_VERSIONS` (released versions) or `DRAFT_PROTOCOL_VERSIONS` (draft versions); unknown strings throw. Listing a draft version additionally requires `allowDraftVersions: true`.

| v1 | v2 |
| --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `supportedProtocolVersions: ['<any string>']` (accepted) | Released or known draft versions only; unknown strings throw at construction |
| — (draft versions did not exist as a concept) | `supportedProtocolVersions: [..., DRAFT_PROTOCOL_VERSION_2026], allowDraftVersions: true` (two-key opt-in) |

## 14. Runtime-Specific JSON Schema Validators (Enhancement)

The SDK now auto-selects the appropriate JSON Schema validator based on runtime:
Expand Down
27 changes: 27 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,33 @@ const client = new Client(
);
```

### `supportedProtocolVersions` is validated at construction

In v1, the `supportedProtocolVersions` option (on `ClientOptions`/`ServerOptions`) accepted arbitrary version strings. In v2, the constructor validates the list: every entry must be a released protocol version (one of `SUPPORTED_PROTOCOL_VERSIONS`) or a known draft version (one of `DRAFT_PROTOCOL_VERSIONS`). Unknown strings throw. Draft versions additionally
require the explicit `allowDraftVersions: true` opt-in — and even then they are never negotiated by default; the opt-in only makes the configuration constructible.

**Before (v1):**

```typescript
// Any version string was accepted
const server = new Server({ name: 'my-server', version: '1.0.0' }, { supportedProtocolVersions: ['2099-01-01'] });
```

**After (v2):**

```typescript
import { DRAFT_PROTOCOL_VERSION_2026, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server';

// Released versions work as before; draft versions require the two-key opt-in
const server = new Server(
{ name: 'my-server', version: '1.0.0' },
{
supportedProtocolVersions: [...SUPPORTED_PROTOCOL_VERSIONS, DRAFT_PROTOCOL_VERSION_2026],
allowDraftVersions: true
}
);
```

### `InMemoryTransport` moved

`InMemoryTransport` is now exported from `@modelcontextprotocol/client` and `@modelcontextprotocol/server` (both re-export it). It is still intended for in-process client-server connections and testing.
Expand Down
6 changes: 6 additions & 0 deletions docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ const transport = new StdioServerTransport();
await server.connect(transport);
```

### Protocol versions

A server negotiates the protocol version per connection from its supported list — by default the released versions in {@linkcode @modelcontextprotocol/server!index.SUPPORTED_PROTOCOL_VERSIONS | SUPPORTED_PROTOCOL_VERSIONS}. Pass `supportedProtocolVersions` in the server options to restrict or reorder that list; every entry must be a released version or a known draft version, otherwise the constructor throws.

Draft (unreleased) protocol revisions, listed in {@linkcode @modelcontextprotocol/server!index.DRAFT_PROTOCOL_VERSIONS | DRAFT_PROTOCOL_VERSIONS}, never appear in the default set and require a two-key opt-in: list the draft version in `supportedProtocolVersions` **and** set `allowDraftVersions: true`. With only one of the two keys, construction throws. The opt-in only makes the configuration constructible — the SDK does not yet negotiate or serve draft protocol versions, so an opted-in server still serves released-protocol traffic.

## Server instructions

Instructions describe how to use the server and its features — cross-tool relationships, workflow patterns, and constraints (see [Instructions](https://modelcontextprotocol.io/specification/latest/basic/lifecycle#instructions) in the MCP specification). Clients may add them to the system prompt. Instructions should not duplicate information already in tool descriptions.
Expand Down
20 changes: 15 additions & 5 deletions examples/server/src/customProtocolVersion.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
/**
* Example: Custom Protocol Version Support
*
* This demonstrates how to support protocol versions not yet in the SDK.
* First version in the list is used as fallback when client requests
* This demonstrates how to customize the protocol versions a server negotiates.
* The supported list may contain released protocol versions (SUPPORTED_PROTOCOL_VERSIONS)
* and — with the explicit allowDraftVersions opt-in — draft versions (DRAFT_PROTOCOL_VERSIONS).
* Unknown version strings are rejected at construction.
*
* First version in the list is used as fallback when a client requests
* an unsupported version.
*
* Run with: pnpm tsx src/customProtocolVersion.ts
Expand All @@ -13,15 +17,21 @@ import { createServer } from 'node:http';

import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
import type { CallToolResult } from '@modelcontextprotocol/server';
import { McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server';
import { DRAFT_PROTOCOL_VERSION_2026, McpServer, SUPPORTED_PROTOCOL_VERSIONS } from '@modelcontextprotocol/server';

// Add support for a newer protocol version (first in list is fallback)
const CUSTOM_VERSIONS = ['2026-01-01', ...SUPPORTED_PROTOCOL_VERSIONS];
// Opt in to the draft protocol revision in addition to all released versions.
// Two keys are required: the draft version must be listed explicitly AND
// allowDraftVersions must be true — otherwise construction throws.
// Note: the opt-in only makes this configuration constructible; the SDK does not
// yet negotiate or serve draft protocol versions, so this server still serves
// released-protocol traffic.
const CUSTOM_VERSIONS = [...SUPPORTED_PROTOCOL_VERSIONS, DRAFT_PROTOCOL_VERSION_2026];

const server = new McpServer(
{ name: 'custom-protocol-server', version: '1.0.0' },
{
supportedProtocolVersions: CUSTOM_VERSIONS,
allowDraftVersions: true,
capabilities: { tools: {} }
}
);
Expand Down
29 changes: 29 additions & 0 deletions packages/client/test/client/client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { DRAFT_PROTOCOL_VERSION_2026, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/core';

import { Client } from '../../src/client/client.js';

describe('Client', () => {
// The validation itself lives in the shared Protocol constructor (covered in depth by the core
// package's protocol.test.ts); these are smoke tests that ClientOptions passes both keys through.
describe('draft protocol version opt-in (allowDraftVersions)', () => {
it('throws at construction when a draft version is listed without allowDraftVersions', () => {
const construct = () =>
new Client({ name: 'test-client', version: '1.0.0' }, { supportedProtocolVersions: [DRAFT_PROTOCOL_VERSION_2026] });

expect(construct).toThrow(DRAFT_PROTOCOL_VERSION_2026);
expect(construct).toThrow('allowDraftVersions');
});

it('constructs when a draft version is listed and allowDraftVersions is true', () => {
const client = new Client(
{ name: 'test-client', version: '1.0.0' },
{
supportedProtocolVersions: [LATEST_PROTOCOL_VERSION, DRAFT_PROTOCOL_VERSION_2026],
allowDraftVersions: true
}
);

expect(client).toBeInstanceOf(Client);
});
});
});
2 changes: 2 additions & 0 deletions packages/core/src/exports/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export * from '../../types/types.js';
// Constants
export {
DEFAULT_NEGOTIATED_PROTOCOL_VERSION,
DRAFT_PROTOCOL_VERSION_2026,
DRAFT_PROTOCOL_VERSIONS,
INTERNAL_ERROR,
INVALID_PARAMS,
INVALID_REQUEST,
Expand Down
48 changes: 48 additions & 0 deletions packages/core/src/shared/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import type {
} from '../types/index.js';
import {
DEFAULT_NEGOTIATED_PROTOCOL_VERSION,
DRAFT_PROTOCOL_VERSIONS,
getNotificationSchema,
getRequestSchema,
getResultSchema,
Expand Down Expand Up @@ -65,10 +66,31 @@ export type ProtocolOptions = {
* Protocol versions supported. First version is preferred (sent by client,
* used as fallback by server). Passed to transport during {@linkcode Protocol.connect | connect()}.
*
* Every listed version must be either a released protocol version (one of
* {@linkcode SUPPORTED_PROTOCOL_VERSIONS}) or a known draft version (one of
* {@linkcode DRAFT_PROTOCOL_VERSIONS}); an unknown version string makes the constructor throw.
* Listing a draft version additionally requires {@linkcode ProtocolOptions.allowDraftVersions | allowDraftVersions}.
*
* @default {@linkcode SUPPORTED_PROTOCOL_VERSIONS}
*/
supportedProtocolVersions?: string[];

/**
* Opt-in for draft (unreleased) protocol versions.
*
* Draft versions use a two-key model: a draft version (one of {@linkcode DRAFT_PROTOCOL_VERSIONS})
* must be listed explicitly in {@linkcode ProtocolOptions.supportedProtocolVersions | supportedProtocolVersions}
* AND this flag must be `true`. Listing a draft version without this flag makes the constructor
* throw; setting this flag without listing a draft version has no effect.
*
* Opting in only makes the configuration constructible. Draft versions are never part of the
* default supported set, and the SDK does not yet negotiate or serve draft protocol versions —
* an opted-in client or server still speaks the released protocol.
*
* @default false
*/
allowDraftVersions?: boolean;

/**
* Whether to restrict emitted requests to only those that the remote side has indicated that they can handle, through their advertised capabilities.
*
Expand Down Expand Up @@ -341,6 +363,29 @@ type TimeoutInfo = {
onTimeout: () => void;
};

/**
* Validates a user-supplied `supportedProtocolVersions` list: every entry must be a released
* protocol version or a known draft version, and draft versions require the `allowDraftVersions`
* opt-in (the two-key model — see {@linkcode ProtocolOptions.allowDraftVersions}).
*/
function validateSupportedProtocolVersions(versions: string[], allowDraftVersions: boolean): void {
for (const version of versions) {
if (DRAFT_PROTOCOL_VERSIONS.includes(version)) {
if (!allowDraftVersions) {
throw new Error(
`Protocol version '${version}' is a draft version: listing it in supportedProtocolVersions additionally requires the allowDraftVersions option to be true`
);
}
} else if (!SUPPORTED_PROTOCOL_VERSIONS.includes(version)) {
throw new Error(
`Unknown protocol version '${version}' in supportedProtocolVersions: it is neither a released protocol version (${SUPPORTED_PROTOCOL_VERSIONS.join(
', '
)}) nor a known draft version (${DRAFT_PROTOCOL_VERSIONS.join(', ')})`
);
}
}
}

/**
* Implements MCP protocol framing on top of a pluggable transport, including
* features like request/response linking, notifications, and progress.
Expand Down Expand Up @@ -395,6 +440,9 @@ export abstract class Protocol<ContextT extends BaseContext> {
fallbackNotificationHandler?: (notification: Notification) => Promise<void>;

constructor(private _options?: ProtocolOptions) {
if (_options?.supportedProtocolVersions) {
validateSupportedProtocolVersions(_options.supportedProtocolVersions, _options.allowDraftVersions === true);
}
this._supportedProtocolVersions = _options?.supportedProtocolVersions ?? SUPPORTED_PROTOCOL_VERSIONS;

// Create TaskManager from protocol options
Expand Down
22 changes: 22 additions & 0 deletions packages/core/src/types/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@ export const LATEST_PROTOCOL_VERSION = '2025-11-25';
export const DEFAULT_NEGOTIATED_PROTOCOL_VERSION = '2025-03-26';
export const SUPPORTED_PROTOCOL_VERSIONS = [LATEST_PROTOCOL_VERSION, '2025-06-18', '2025-03-26', '2024-11-05', '2024-10-07'];

/**
* The wire identifier of the draft (unreleased) protocol revision.
*
* The literal mirrors `LATEST_PROTOCOL_VERSION` in the draft specification schema
* (https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.ts),
* which is the source of truth for this value.
*
* Draft protocol versions are not negotiable and require explicit opt-in: they never appear in
* {@linkcode SUPPORTED_PROTOCOL_VERSIONS}, and listing one in `supportedProtocolVersions`
* additionally requires the `allowDraftVersions` option to be `true` (otherwise construction throws).
*/
export const DRAFT_PROTOCOL_VERSION_2026 = 'DRAFT-2026-v1';

/**
* All draft (unreleased) protocol revisions known to this SDK.
*
* Draft versions are kept separate from {@linkcode SUPPORTED_PROTOCOL_VERSIONS}: they are never
* negotiated or served by default, and may only be listed in `supportedProtocolVersions` together
* with the explicit `allowDraftVersions` opt-in.
*/
export const DRAFT_PROTOCOL_VERSIONS: readonly string[] = [DRAFT_PROTOCOL_VERSION_2026];

export const RELATED_TASK_META_KEY = 'io.modelcontextprotocol/related-task';

/* JSON-RPC types */
Expand Down
Loading
Loading