Skip to content

Commit 1215a61

Browse files
committed
feat: check for CIMD of authorization server metadata
1 parent 70cc5ef commit 1215a61

7 files changed

Lines changed: 138 additions & 17 deletions

File tree

src/checks/checks.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ describe('createClientInitializationCheck', () => {
1515
expect(check.errorMessage).toBeUndefined();
1616
});
1717

18-
it('should return FAILURE when protocol version is missing', () => {
18+
it('should return WARNING when protocol version is missing', () => {
1919
const invalidRequest = {
2020
clientInfo: {
2121
name: 'TestClient',
@@ -24,11 +24,11 @@ describe('createClientInitializationCheck', () => {
2424
};
2525

2626
const check = createClientInitializationCheck(invalidRequest);
27-
expect(check.status).toBe('FAILURE');
27+
expect(check.status).toBe('WARNING');
2828
expect(check.errorMessage).toContain('Protocol version not provided');
2929
});
3030

31-
it('should return FAILURE when protocol version does not match', () => {
31+
it('should return WARNING when protocol version does not match', () => {
3232
const invalidRequest = {
3333
protocolVersion: '2024-11-05',
3434
clientInfo: {
@@ -38,11 +38,11 @@ describe('createClientInitializationCheck', () => {
3838
};
3939

4040
const check = createClientInitializationCheck(invalidRequest);
41-
expect(check.status).toBe('FAILURE');
41+
expect(check.status).toBe('WARNING');
4242
expect(check.errorMessage).toContain('Version mismatch');
4343
});
4444

45-
it('should return FAILURE when client name is missing', () => {
45+
it('should return WARNING when client name is missing', () => {
4646
const invalidRequest = {
4747
protocolVersion: '2025-06-18',
4848
clientInfo: {
@@ -51,11 +51,11 @@ describe('createClientInitializationCheck', () => {
5151
};
5252

5353
const check = createClientInitializationCheck(invalidRequest);
54-
expect(check.status).toBe('FAILURE');
54+
expect(check.status).toBe('WARNING');
5555
expect(check.errorMessage).toContain('Client name missing');
5656
});
5757

58-
it('should return FAILURE when client version is missing', () => {
58+
it('should return WARNING when client version is missing', () => {
5959
const invalidRequest = {
6060
protocolVersion: '2025-06-18',
6161
clientInfo: {
@@ -64,7 +64,7 @@ describe('createClientInitializationCheck', () => {
6464
};
6565

6666
const check = createClientInitializationCheck(invalidRequest);
67-
expect(check.status).toBe('FAILURE');
67+
expect(check.status).toBe('WARNING');
6868
expect(check.errorMessage).toContain('Client version missing');
6969
});
7070

src/checks/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function createClientInitializationCheck(
4848
if (!initializeRequest?.clientInfo?.version)
4949
errors.push('Client version missing');
5050

51-
const status: CheckStatus = errors.length === 0 ? 'SUCCESS' : 'FAILURE';
51+
const status: CheckStatus = errors.length === 0 ? 'SUCCESS' : 'WARNING';
5252

5353
return {
5454
id: 'mcp-client-initialization',

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,8 @@ program
495495
const result = await runAuthorizationServerConformanceTest(
496496
validated.url,
497497
scenarioName,
498-
outputDir
498+
outputDir,
499+
specVersionFilter
499500
);
500501
allResults.push({ scenario: scenarioName, checks: result.checks });
501502
} catch (error) {

src/runner/authorization-server.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { promises as fs } from 'fs';
22
import path from 'path';
3-
import { ConformanceCheck } from '../types';
3+
import { ConformanceCheck, SpecVersion } from '../types';
44
import { getClientScenarioForAuthorizationServer } from '../scenarios';
55
import { createResultDir } from './utils';
66

77
export async function runAuthorizationServerConformanceTest(
88
serverUrl: string,
99
scenarioName: string,
10-
outputDir?: string
10+
outputDir?: string,
11+
specVersion?: SpecVersion
1112
): Promise<{
1213
checks: ConformanceCheck[];
1314
resultDir?: string;
@@ -31,7 +32,7 @@ export async function runAuthorizationServerConformanceTest(
3132
`Running client scenario for authorization server '${scenarioName}' against server: ${serverUrl}`
3233
);
3334

34-
const checks = await scenario.run(serverUrl);
35+
const checks = await scenario.run(serverUrl, specVersion);
3536

3637
if (resultDir) {
3738
await fs.writeFile(

src/scenarios/authorization-server/authorization-server-metadata.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,84 @@ describe('AuthorizationServerMetadataEndpointScenario', () => {
8888
expect(check.status).toBe('FAILURE');
8989
expect(check.errorMessage).toContain('code_challenge_methods_supported');
9090
});
91+
92+
it('returns SUCCESS for CIMD check when server metadata includes client_id_metadata_document_supported=true with spec version 2025-11-25', async () => {
93+
const scenario = new AuthorizationServerMetadataEndpointScenario();
94+
mockMetadataResponse({
95+
...validMetadata,
96+
client_id_metadata_document_supported: true
97+
});
98+
99+
const checks = await scenario.run(SERVER_URL, '2025-11-25');
100+
101+
expect(checks).toHaveLength(2);
102+
103+
const metadataCheck = checks[0];
104+
expect(metadataCheck.status).toBe('SUCCESS');
105+
106+
const cimdCheck = checks[1];
107+
expect(cimdCheck.id).toBe('authorization-server-metadata-cimd');
108+
expect(cimdCheck.status).toBe('SUCCESS');
109+
expect(cimdCheck.errorMessage).toBeUndefined();
110+
expect(cimdCheck.details).toEqual({
111+
client_id_metadata_document_supported: true
112+
});
113+
});
114+
115+
it('returns FAILURE for CIMD check when server metadata lacks client_id_metadata_document_supported with spec version 2025-11-25', async () => {
116+
const scenario = new AuthorizationServerMetadataEndpointScenario();
117+
mockMetadataResponse(validMetadata);
118+
119+
const checks = await scenario.run(SERVER_URL, '2025-11-25');
120+
121+
expect(checks).toHaveLength(2);
122+
123+
const metadataCheck = checks[0];
124+
expect(metadataCheck.status).toBe('SUCCESS');
125+
126+
const cimdCheck = checks[1];
127+
expect(cimdCheck.id).toBe('authorization-server-metadata-cimd');
128+
expect(cimdCheck.status).toBe('FAILURE');
129+
expect(cimdCheck.errorMessage).toContain(
130+
'client_id_metadata_document_supported'
131+
);
132+
});
133+
134+
it('returns FAILURE for CIMD check when client_id_metadata_document_supported is false with spec version 2025-11-25', async () => {
135+
const scenario = new AuthorizationServerMetadataEndpointScenario();
136+
mockMetadataResponse({
137+
...validMetadata,
138+
client_id_metadata_document_supported: false
139+
});
140+
141+
const checks = await scenario.run(SERVER_URL, '2025-11-25');
142+
143+
expect(checks).toHaveLength(2);
144+
145+
const metadataCheck = checks[0];
146+
expect(metadataCheck.status).toBe('SUCCESS');
147+
148+
const cimdCheck = checks[1];
149+
expect(cimdCheck.id).toBe('authorization-server-metadata-cimd');
150+
expect(cimdCheck.status).toBe('FAILURE');
151+
expect(cimdCheck.errorMessage).toContain(
152+
'client_id_metadata_document_supported'
153+
);
154+
expect(cimdCheck.errorMessage).toContain('false');
155+
});
156+
157+
it('does not add CIMD check when spec version is 2025-06-18 even if claim is false', async () => {
158+
const scenario = new AuthorizationServerMetadataEndpointScenario();
159+
mockMetadataResponse({
160+
...validMetadata,
161+
client_id_metadata_document_supported: false
162+
});
163+
164+
const checks = await scenario.run(SERVER_URL, '2025-06-18');
165+
166+
expect(checks).toHaveLength(1);
167+
168+
const metadataCheck = checks[0];
169+
expect(metadataCheck.status).toBe('SUCCESS');
170+
});
91171
});

src/scenarios/authorization-server/authorization-server-metadata.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,15 @@ export class AuthorizationServerMetadataEndpointScenario implements ClientScenar
2525
- Return a JSON response including issuer, authorization_endpoint, token_endpoint and response_types_supported
2626
- The issuer value MUST match the URI obtained by removing the well-known URI string from the authorization server metadata URI.`;
2727

28-
async run(serverUrl: string): Promise<ConformanceCheck[]> {
28+
async run(
29+
serverUrl: string,
30+
specVersion?: SpecVersion
31+
): Promise<ConformanceCheck[]> {
2932
let status: Status = 'SUCCESS';
3033
let errorMessage: string | undefined;
3134
let details: any;
3235
let response: any | null = null;
36+
let body: Record<string, any> | undefined;
3337
try {
3438
const wellKnownUrls = this.createWellKnownUrl(serverUrl);
3539

@@ -53,7 +57,7 @@ export class AuthorizationServerMetadataEndpointScenario implements ClientScenar
5357

5458
this.validateContentType(response.headers['content-type']);
5559

56-
const body = await this.parseJson(response);
60+
body = await this.parseJson(response);
5761
const errors: string[] = [];
5862
this.validateMetadataBody(body, serverUrl, errors);
5963

@@ -71,7 +75,7 @@ export class AuthorizationServerMetadataEndpointScenario implements ClientScenar
7175
errorMessage = error instanceof Error ? error.message : String(error);
7276
}
7377

74-
return [
78+
const checks: ConformanceCheck[] = [
7579
{
7680
id: 'authorization-server-metadata',
7781
name: 'AuthorizationServerMetadata',
@@ -88,6 +92,38 @@ export class AuthorizationServerMetadataEndpointScenario implements ClientScenar
8892
...(details ? { details } : {})
8993
}
9094
];
95+
96+
if (specVersion === '2025-11-25' && body) {
97+
const cimdSupported = body.client_id_metadata_document_supported;
98+
const cimdStatus: Status = cimdSupported === true ? 'SUCCESS' : 'FAILURE';
99+
const cimdErrorMessage =
100+
cimdSupported === true
101+
? undefined
102+
: cimdSupported === undefined
103+
? 'Authorization server metadata does not include "client_id_metadata_document_supported"'
104+
: `Expected "client_id_metadata_document_supported" to be true, got ${JSON.stringify(cimdSupported)}`;
105+
106+
checks.push({
107+
id: 'authorization-server-metadata-cimd',
108+
name: 'AuthorizationServerMetadataCIMD',
109+
description:
110+
'Authorization server metadata includes client_id_metadata_document_supported=true',
111+
status: cimdStatus,
112+
timestamp: new Date().toISOString(),
113+
errorMessage: cimdErrorMessage,
114+
specReferences: [
115+
{
116+
id: 'IETF-OAuth-Client-ID-Metadata-Document',
117+
url: 'https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-01.html#name-authorization-server-metada'
118+
}
119+
],
120+
details: {
121+
client_id_metadata_document_supported: cimdSupported
122+
}
123+
});
124+
}
125+
126+
return checks;
91127
}
92128

93129
private createWellKnownUrl(serverUrl: string): string[] {

src/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,8 @@ export interface ClientScenarioForAuthorizationServer {
6565
name: string;
6666
description: string;
6767
specVersions: SpecVersion[];
68-
run(serverUrl: string): Promise<ConformanceCheck[]>;
68+
run(
69+
serverUrl: string,
70+
specVersion?: SpecVersion
71+
): Promise<ConformanceCheck[]>;
6972
}

0 commit comments

Comments
 (0)