Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/constructs/interfaces/McpAuthOptions.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
Requires explicit endpoint URLs and client ID.</p>
</div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="lambdaoptions"><code class="tsd-tag">Optional</code> <code class="tsd-tag">Readonly</code><span>lambda<wbr/>Options</span><a href="#lambdaoptions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">lambdaOptions</span><span class="tsd-signature-symbol">?:</span> <a href="../types/LambdaOptions.html" class="tsd-signature-type tsd-kind-type-alias">LambdaOptions</a></div><div class="tsd-comment tsd-typography"><p>Lambda function options for MCP auth handlers.</p>
</div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="protocolversions"><code class="tsd-tag">Optional</code> <code class="tsd-tag">Readonly</code><span>protocol<wbr/>Versions</span><a href="#protocolversions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">protocolVersions</span><span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">[]</span></div><div class="tsd-comment tsd-typography"><p>Supported MCP protocol versions (newest first).</p>
</div><div class="tsd-comment tsd-typography"><div class="tsd-tag-default"><h4 class="tsd-anchor-link" id="default-1">Default<a href="#default-1" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><pre><code class="ts"><span class="hl-1">[</span><span class="hl-2">&#39;2025-11-25&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2025-03-26&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2024-11-05&#39;</span><span class="hl-1">]</span>
</div><div class="tsd-comment tsd-typography"><div class="tsd-tag-default"><h4 class="tsd-anchor-link" id="default-1">Default<a href="#default-1" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><pre><code class="ts"><span class="hl-1">[</span><span class="hl-2">&#39;2025-11-25&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2025-06-18&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2025-03-26&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2024-11-05&#39;</span><span class="hl-1">]</span>
</code><button type="button">Copy</button></pre>

</div></div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="scopes"><code class="tsd-tag">Optional</code> <code class="tsd-tag">Readonly</code><span>scopes</span><a href="#scopes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">scopes</span><span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">[]</span></div><div class="tsd-comment tsd-typography"><p>OAuth scopes to advertise in discovery metadata.</p>
Expand Down
2 changes: 1 addition & 1 deletion docs/constructs/interfaces/McpAuthProps.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
</div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="clientid"><code class="tsd-tag">Readonly</code><span>client<wbr/>Id</span><a href="#clientid" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">clientId</span><span class="tsd-signature-symbol">:</span> <span class="tsd-signature-type">string</span></div><div class="tsd-comment tsd-typography"><p>The pre-provisioned OAuth client ID returned by the register endpoint.</p>
</div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="lambdaoptions"><code class="tsd-tag">Optional</code> <code class="tsd-tag">Readonly</code><span>lambda<wbr/>Options</span><a href="#lambdaoptions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">lambdaOptions</span><span class="tsd-signature-symbol">?:</span> <a href="../types/LambdaOptions.html" class="tsd-signature-type tsd-kind-type-alias">LambdaOptions</a></div><div class="tsd-comment tsd-typography"><p>Lambda function options for MCP auth handlers.</p>
</div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="protocolversions"><code class="tsd-tag">Optional</code> <code class="tsd-tag">Readonly</code><span>protocol<wbr/>Versions</span><a href="#protocolversions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">protocolVersions</span><span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">[]</span></div><div class="tsd-comment tsd-typography"><p>Supported MCP protocol versions (newest first).</p>
</div><div class="tsd-comment tsd-typography"><div class="tsd-tag-default"><h4 class="tsd-anchor-link" id="default">Default<a href="#default" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><pre><code class="ts"><span class="hl-1">[</span><span class="hl-2">&#39;2025-11-25&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2025-03-26&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2024-11-05&#39;</span><span class="hl-1">]</span>
</div><div class="tsd-comment tsd-typography"><div class="tsd-tag-default"><h4 class="tsd-anchor-link" id="default">Default<a href="#default" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><pre><code class="ts"><span class="hl-1">[</span><span class="hl-2">&#39;2025-11-25&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2025-06-18&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2025-03-26&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2024-11-05&#39;</span><span class="hl-1">]</span>
</code><button type="button">Copy</button></pre>

</div></div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="scopes"><code class="tsd-tag">Optional</code> <code class="tsd-tag">Readonly</code><span>scopes</span><a href="#scopes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">scopes</span><span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">[]</span></div><div class="tsd-comment tsd-typography"><p>OAuth scopes to advertise in discovery metadata.</p>
Expand Down
2 changes: 1 addition & 1 deletion docs/constructs/interfaces/McpCognitoAuthProps.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

</div></div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="lambdaoptions"><code class="tsd-tag">Optional</code> <code class="tsd-tag">Readonly</code><span>lambda<wbr/>Options</span><a href="#lambdaoptions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">lambdaOptions</span><span class="tsd-signature-symbol">?:</span> <a href="../types/LambdaOptions.html" class="tsd-signature-type tsd-kind-type-alias">LambdaOptions</a></div><div class="tsd-comment tsd-typography"><p>Lambda function options for MCP auth handlers.</p>
</div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="protocolversions"><code class="tsd-tag">Optional</code> <code class="tsd-tag">Readonly</code><span>protocol<wbr/>Versions</span><a href="#protocolversions" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">protocolVersions</span><span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">[]</span></div><div class="tsd-comment tsd-typography"><p>Supported MCP protocol versions (newest first).</p>
</div><div class="tsd-comment tsd-typography"><div class="tsd-tag-default"><h4 class="tsd-anchor-link" id="default-2">Default<a href="#default-2" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><pre><code class="ts"><span class="hl-1">[</span><span class="hl-2">&#39;2025-11-25&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2025-03-26&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2024-11-05&#39;</span><span class="hl-1">]</span>
</div><div class="tsd-comment tsd-typography"><div class="tsd-tag-default"><h4 class="tsd-anchor-link" id="default-2">Default<a href="#default-2" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h4><pre><code class="ts"><span class="hl-1">[</span><span class="hl-2">&#39;2025-11-25&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2025-06-18&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2025-03-26&#39;</span><span class="hl-1">, </span><span class="hl-2">&#39;2024-11-05&#39;</span><span class="hl-1">]</span>
</code><button type="button">Copy</button></pre>

</div></div></section><section class="tsd-panel tsd-member"><h3 class="tsd-anchor-link" id="scopes"><code class="tsd-tag">Optional</code> <code class="tsd-tag">Readonly</code><span>scopes</span><a href="#scopes" aria-label="Permalink" class="tsd-anchor-icon"><svg viewBox="0 0 24 24" aria-hidden="true"><use href="../assets/icons.svg#icon-anchor"></use></svg></a></h3><div class="tsd-signature"><span class="tsd-kind-property">scopes</span><span class="tsd-signature-symbol">?:</span> <span class="tsd-signature-type">string</span><span class="tsd-signature-symbol">[]</span></div><div class="tsd-comment tsd-typography"><p>OAuth scopes to advertise in discovery metadata.
Expand Down
4 changes: 2 additions & 2 deletions src/constructs/mcp-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];

Expand Down Expand Up @@ -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(','),
Expand Down
2 changes: 1 addition & 1 deletion src/constructs/mcp-cognito-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];

Expand Down
2 changes: 1 addition & 1 deletion src/constructs/rest-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];

Expand Down
39 changes: 37 additions & 2 deletions src/mcp-auth/mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,24 @@ export function createMcpServer<TPrincipal>(options: McpServerOptions<TPrincipal
function handleInitialize(id: unknown, params: unknown): McpResponse {
const clientVersion = (params as Record<string, unknown> | 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,
}));
Expand Down Expand Up @@ -120,3 +132,26 @@ export function createMcpServer<TPrincipal>(options: McpServerOptions<TPrincipal
};
}
}

/**
* Selects the protocol version to use for the handshake.
*
* Returns the requested version when supported. Otherwise returns the highest
* supported version that is not newer than the requested one (downward
* negotiation). Returns undefined when every supported version is newer than
* the requested one, i.e. no safe downgrade exists.
*
* MCP protocol versions are ISO-like `YYYY-MM-DD` strings, so lexicographic
* comparison matches chronological order.
*/
export function negotiateProtocolVersion(requested: string, supported: string[]): string | undefined {
if (supported.includes(requested)) {
return requested;
}

const candidates = supported
.filter((v) => v <= requested)
.sort();

return candidates.length > 0 ? candidates[candidates.length - 1] : undefined;
}
2 changes: 1 addition & 1 deletion test/constructs/mcp-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
52 changes: 50 additions & 2 deletions test/mcp-auth/mcp-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }),
{},
Expand Down