diff --git a/docs/constructs/interfaces/McpAuthOptions.html b/docs/constructs/interfaces/McpAuthOptions.html index f444c5a6..3090a1b9 100644 --- a/docs/constructs/interfaces/McpAuthOptions.html +++ b/docs/constructs/interfaces/McpAuthOptions.html @@ -18,7 +18,7 @@ Requires explicit endpoint URLs and client ID.

lambdaOptions?: LambdaOptions

Lambda function options for MCP auth handlers.

protocolVersions?: string[]

Supported MCP protocol versions (newest first).

-
['2025-11-25', '2025-03-26', '2024-11-05']
+
['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05']
 
scopes?: string[]

OAuth scopes to advertise in discovery metadata.

diff --git a/docs/constructs/interfaces/McpAuthProps.html b/docs/constructs/interfaces/McpAuthProps.html index d808c7ae..07f92798 100644 --- a/docs/constructs/interfaces/McpAuthProps.html +++ b/docs/constructs/interfaces/McpAuthProps.html @@ -24,7 +24,7 @@
clientId: string

The pre-provisioned OAuth client ID returned by the register endpoint.

lambdaOptions?: LambdaOptions

Lambda function options for MCP auth handlers.

protocolVersions?: string[]

Supported MCP protocol versions (newest first).

-
['2025-11-25', '2025-03-26', '2024-11-05']
+
['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05']
 
scopes?: string[]

OAuth scopes to advertise in discovery metadata.

diff --git a/docs/constructs/interfaces/McpCognitoAuthProps.html b/docs/constructs/interfaces/McpCognitoAuthProps.html index 16502e69..8046ad30 100644 --- a/docs/constructs/interfaces/McpCognitoAuthProps.html +++ b/docs/constructs/interfaces/McpCognitoAuthProps.html @@ -34,7 +34,7 @@
lambdaOptions?: LambdaOptions

Lambda function options for MCP auth handlers.

protocolVersions?: string[]

Supported MCP protocol versions (newest first).

-
['2025-11-25', '2025-03-26', '2024-11-05']
+
['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05']
 
scopes?: string[]

OAuth scopes to advertise in discovery metadata. diff --git a/src/constructs/mcp-auth.ts b/src/constructs/mcp-auth.ts index a030607e..d1e43e0f 100644 --- a/src/constructs/mcp-auth.ts +++ b/src/constructs/mcp-auth.ts @@ -47,7 +47,7 @@ export interface McpAuthProps { /** * Supported MCP protocol versions (newest first). - * @default ['2025-11-25', '2025-03-26', '2024-11-05'] + * @default ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'] */ readonly protocolVersions?: string[]; @@ -139,7 +139,7 @@ export class McpAuth extends Construct { MCP_CLIENT_ID: props.clientId, MCP_SERVER_NAME: props.serverInfo.name, MCP_SERVER_VERSION: props.serverInfo.version, - MCP_PROTOCOL_VERSIONS: (props.protocolVersions ?? ['2025-11-25', '2025-03-26', '2024-11-05']).join(','), + MCP_PROTOCOL_VERSIONS: (props.protocolVersions ?? ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05']).join(','), MCP_SCOPES: (props.scopes ?? ['openid', 'email', 'profile']).join(','), MCP_ALLOWED_REDIRECT_URIS: props.allowedRedirectUris.join(','), MCP_STRIP_PARAMETERS: (props.stripParameters ?? ['resource']).join(','), diff --git a/src/constructs/mcp-cognito-auth.ts b/src/constructs/mcp-cognito-auth.ts index a17ef3a8..c011c952 100644 --- a/src/constructs/mcp-cognito-auth.ts +++ b/src/constructs/mcp-cognito-auth.ts @@ -42,7 +42,7 @@ export interface McpCognitoAuthProps { /** * Supported MCP protocol versions (newest first). - * @default ['2025-11-25', '2025-03-26', '2024-11-05'] + * @default ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'] */ readonly protocolVersions?: string[]; diff --git a/src/constructs/rest-api.ts b/src/constructs/rest-api.ts index 633dda1f..3863e52b 100644 --- a/src/constructs/rest-api.ts +++ b/src/constructs/rest-api.ts @@ -100,7 +100,7 @@ export interface McpAuthOptions { /** * Supported MCP protocol versions (newest first). - * @default ['2025-11-25', '2025-03-26', '2024-11-05'] + * @default ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'] */ readonly protocolVersions?: string[]; diff --git a/src/mcp-auth/mcp/server.ts b/src/mcp-auth/mcp/server.ts index 605d5253..7ee718ea 100644 --- a/src/mcp-auth/mcp/server.ts +++ b/src/mcp-auth/mcp/server.ts @@ -62,12 +62,24 @@ export function createMcpServer(options: McpServerOptions | undefined)?.protocolVersion as string | undefined; - if (!clientVersion || !options.protocolVersions.includes(clientVersion)) { + if (!clientVersion) { + return jsonResponse(200, jsonRpcError(id, INVALID_PARAMS, `Unsupported protocol version. Supported: ${options.protocolVersions.join(', ')}`)); + } + + // Per the MCP lifecycle spec: echo the requested version when we support it, + // otherwise negotiate to a version we do support. We negotiate *downwards* — + // the highest supported version that is not newer than the one requested — + // because clients (e.g. @modelcontextprotocol/sdk <= 1.16.0) reject any + // negotiated version outside their own accepted set. Answering with a newer + // version than the client asked for would fail the handshake. + const negotiatedVersion = negotiateProtocolVersion(clientVersion, options.protocolVersions); + + if (!negotiatedVersion) { return jsonResponse(200, jsonRpcError(id, INVALID_PARAMS, `Unsupported protocol version. Supported: ${options.protocolVersions.join(', ')}`)); } return jsonResponse(200, jsonRpcSuccess(id, { - protocolVersion: clientVersion, + protocolVersion: negotiatedVersion, capabilities: { tools: {} }, serverInfo: options.serverInfo, })); @@ -120,3 +132,26 @@ export function createMcpServer(options: McpServerOptions v <= requested) + .sort(); + + return candidates.length > 0 ? candidates[candidates.length - 1] : undefined; +} diff --git a/test/constructs/mcp-auth.test.ts b/test/constructs/mcp-auth.test.ts index f772b3a1..0679f4f5 100644 --- a/test/constructs/mcp-auth.test.ts +++ b/test/constructs/mcp-auth.test.ts @@ -129,7 +129,7 @@ describe('McpAuth', () => { MCP_CLIENT_ID: 'test-client-id', MCP_SERVER_NAME: 'test-server', MCP_SERVER_VERSION: '1.0.0', - MCP_PROTOCOL_VERSIONS: '2025-11-25,2025-03-26,2024-11-05', + MCP_PROTOCOL_VERSIONS: '2025-11-25,2025-06-18,2025-03-26,2024-11-05', MCP_SCOPES: 'openid,email,profile', MCP_ALLOWED_REDIRECT_URIS: 'https://claude.ai/oauth/callback', MCP_STRIP_PARAMETERS: 'resource', diff --git a/test/mcp-auth/mcp-server.test.ts b/test/mcp-auth/mcp-server.test.ts index f635e10a..e7cc79c1 100644 --- a/test/mcp-auth/mcp-server.test.ts +++ b/test/mcp-auth/mcp-server.test.ts @@ -60,8 +60,56 @@ describe('createMcpServer', () => { expect(body.result.protocolVersion).toBe('2025-03-26'); }); - test('rejects unsupported protocol version', async () => { - const server = makeServer(); + test('echoes back 2025-06-18 when it is supported', async () => { + const server = makeServer({ + protocolVersions: ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'], + }); + const result = await server.handle( + rpcBody('initialize', { protocolVersion: '2025-06-18' }), + {}, + ); + + expect(result.statusCode).toBe(200); + const body = JSON.parse(result.body); + expect(body.error).toBeUndefined(); + expect(body.result.protocolVersion).toBe('2025-06-18'); + }); + + test('negotiates down to the highest supported version not newer than requested', async () => { + // Client asks for a version we do not support; the closest older version + // we share is 2025-03-26 (2025-06-18 would be newer than the request). + const server = makeServer({ + protocolVersions: ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'], + }); + const result = await server.handle( + rpcBody('initialize', { protocolVersion: '2025-05-01' }), + {}, + ); + + expect(result.statusCode).toBe(200); + const body = JSON.parse(result.body); + expect(body.error).toBeUndefined(); + expect(body.result.protocolVersion).toBe('2025-03-26'); + }); + + test('negotiates down for an unknown future version instead of erroring', async () => { + const server = makeServer({ + protocolVersions: ['2025-11-25', '2025-06-18', '2025-03-26', '2024-11-05'], + }); + const result = await server.handle( + rpcBody('initialize', { protocolVersion: '2030-01-01' }), + {}, + ); + + expect(result.statusCode).toBe(200); + const body = JSON.parse(result.body); + expect(body.error).toBeUndefined(); + // Highest supported version, since all supported versions are older. + expect(body.result.protocolVersion).toBe('2025-11-25'); + }); + + test('errors only when no supported version is old enough to downgrade to', async () => { + const server = makeServer({ protocolVersions: ['2025-11-25', '2025-06-18'] }); const result = await server.handle( rpcBody('initialize', { protocolVersion: '1999-01-01' }), {},