Skip to content

Commit cf72957

Browse files
committed
conformance-tests: upgrade to MCP-security 0.1.11, implement CIMD
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent 87e2c7d commit cf72957

7 files changed

Lines changed: 94 additions & 67 deletions

File tree

conformance-tests/VALIDATION_RESULTS.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
**Server Tests (active suite):** 44/44 passed (31 scenarios, 100%)
66
**Server Tests (spec 2025-11-25):** 4/4 passed — SEP-1613 `json-schema-2020-12` scenario ✨
77
**Client Tests:** 3/4 scenarios passed (9/10 checks passed)
8-
**Auth Tests:** 14/15 scenarios fully passing (196 passed, 0 failed, 1 warning, 93.3% scenarios, 99.5% checks)
8+
**Auth Tests:** 15/15 scenarios fully passing (195 passed, 0 failed, 0 warnings, 100% scenarios, 100% checks)
99

1010
## Server Test Results
1111

@@ -46,16 +46,17 @@
4646

4747
## Auth Test Results (Spring HTTP Client)
4848

49-
**Status: 196 passed, 0 failed, 1 warning across 15 scenarios**
49+
**Status: 195 passed, 0 failed, 0 warnings across 15 scenarios**
5050

5151
Uses the `client-spring-http-client` module with Spring Security OAuth2 and the [mcp-client-security](https://github.com/springaicommunity/mcp-client-security) library.
5252

53-
### Fully Passing (14/15 scenarios)
53+
### Fully Passing (15/15 scenarios)
5454

5555
- **auth/metadata-default (13/13):** Default metadata discovery
5656
- **auth/metadata-var1 (13/13):** Metadata discovery variant 1
5757
- **auth/metadata-var2 (13/13):** Metadata discovery variant 2
5858
- **auth/metadata-var3 (13/13):** Metadata discovery variant 3
59+
- **auth/basic-cimd (12/12):** Basic Client-Initiated Metadata Discovery
5960
- **auth/scope-from-www-authenticate (14/14):** Scope extraction from WWW-Authenticate header
6061
- **auth/scope-from-scopes-supported (14/14):** Scope extraction from scopes_supported
6162
- **auth/scope-omitted-when-undefined (14/14):** Scope omitted when not defined
@@ -67,14 +68,9 @@ Uses the `client-spring-http-client` module with Spring Security OAuth2 and the
6768
- **auth/resource-mismatch (2/2):** Resource mismatch handling
6869
- **auth/pre-registration (6/6):** Pre-registered client credentials flow
6970

70-
### Partially Passing (1/15 scenarios)
71-
72-
- **auth/basic-cimd (13/13 + 1 warning):** Basic Client-Initiated Metadata Discovery — all checks pass, minor warning
73-
7471
## Known Limitations
7572

7673
1. **Client SSE Retry:** Client doesn't parse or respect the `retry:` field, reconnects immediately, and doesn't send Last-Event-ID header
77-
2. **Auth Basic CIMD:** Minor conformance warning in the basic Client-Initiated Metadata Discovery flow
7874

7975
## Running Tests
8076

@@ -132,4 +128,3 @@ npx @modelcontextprotocol/conformance@0.1.15 client \
132128

133129
### High Priority
134130
1. Fix client SSE retry field handling in `HttpClientStreamableHttpTransport`
135-
2. Implement CIMD

conformance-tests/client-spring-http-client/README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,24 @@ Test with @modelcontextprotocol/conformance@0.1.15.
1414

1515
## Conformance Test Results
1616

17-
**Status: 178 passed, 1 failed, 1 warning across 14 scenarios**
17+
**Status: 195 passed, 0 failed, 0 warnings across 15 scenarios**
1818

1919
| Scenario | Result | Details |
2020
|---|---|---|
21-
| auth/metadata-default | ✅ Pass | 12/12 |
22-
| auth/metadata-var1 | ✅ Pass | 12/12 |
23-
| auth/metadata-var2 | ✅ Pass | 12/12 |
24-
| auth/metadata-var3 | ✅ Pass | 12/12 |
25-
| auth/basic-cimd | ⚠️ Warning | 12/12 passed, 1 warning |
26-
| auth/scope-from-www-authenticate | ✅ Pass | 13/13 |
27-
| auth/scope-from-scopes-supported | ✅ Pass | 13/13 |
28-
| auth/scope-omitted-when-undefined | ✅ Pass | 13/13 |
29-
| auth/scope-step-up | ✅ Pass | 12/12 |
21+
| auth/metadata-default | ✅ Pass | 13/13 |
22+
| auth/metadata-var1 | ✅ Pass | 13/13 |
23+
| auth/metadata-var2 | ✅ Pass | 13/13 |
24+
| auth/metadata-var3 | ✅ Pass | 13/13 |
25+
| auth/basic-cimd | ✅ Pass | 12/12 |
26+
| auth/scope-from-www-authenticate | ✅ Pass | 14/14 |
27+
| auth/scope-from-scopes-supported | ✅ Pass | 14/14 |
28+
| auth/scope-omitted-when-undefined | ✅ Pass | 14/14 |
29+
| auth/scope-step-up | ✅ Pass | 16/16 |
3030
| auth/scope-retry-limit | ✅ Pass | 11/11 |
31-
| auth/token-endpoint-auth-basic | ✅ Pass | 17/17 |
32-
| auth/token-endpoint-auth-post | ✅ Pass | 17/17 |
33-
| auth/token-endpoint-auth-none | ✅ Pass | 17/17 |
31+
| auth/token-endpoint-auth-basic | ✅ Pass | 18/18 |
32+
| auth/token-endpoint-auth-post | ✅ Pass | 18/18 |
33+
| auth/token-endpoint-auth-none | ✅ Pass | 18/18 |
34+
| auth/resource-mismatch | ✅ Pass | 2/2 |
3435
| auth/pre-registration | ✅ Pass | 6/6 |
3536

3637
See [VALIDATION_RESULTS.md](../VALIDATION_RESULTS.md) for the full project validation results.
@@ -113,8 +114,7 @@ java -jar conformance-tests/client-spring-http-client/target/client-spring-http-
113114

114115
## Known Issues
115116

116-
1. **auth/scope-step-up** (1 failure) — The client does not fully handle scope step-up challenges where the server requests additional scopes after initial authorization.
117-
2. **auth/basic-cimd** (1 warning) — Minor conformance warning in the basic Client-Initiated Metadata Discovery flow.
117+
Currently, there are no known issues in the auth suite implementation.
118118

119119
## References
120120

conformance-tests/client-spring-http-client/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
<properties>
2424
<java.version>17</java.version>
2525
<spring-boot.version>4.0.5</spring-boot.version>
26-
<spring-ai.version>2.0.0-M4</spring-ai.version>
27-
<spring-ai-mcp-security.version>0.1.5</spring-ai-mcp-security.version>
26+
<spring-ai.version>2.0.0-M6</spring-ai.version>
27+
<spring-ai-mcp-security.version>0.1.11</spring-ai-mcp-security.version>
2828
<maven.deploy.skip>true</maven.deploy.skip>
2929
</properties>
3030

conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/ConformanceSpringClientApplication.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,23 @@
88

99
import io.modelcontextprotocol.conformance.client.scenario.Scenario;
1010
import org.springaicommunity.mcp.security.client.sync.oauth2.metadata.McpMetadataDiscoveryService;
11-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DefaultMcpOAuth2ClientManager;
11+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DefaultMcpOAuth2DcrClientManager;
1212
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DynamicClientRegistrationService;
1313
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.InMemoryMcpClientRegistrationRepository;
1414
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
15-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
15+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2DcrClientManager;
16+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.cimd.DefaultMcpOAuth2CimdClientManager;
17+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.cimd.McpOAuth2CimdClientManager;
18+
import org.springaicommunity.mcp.security.common.url.DefaultUrlValidator;
1619

1720
import org.springframework.boot.ApplicationArguments;
1821
import org.springframework.boot.ApplicationRunner;
1922
import org.springframework.boot.SpringApplication;
2023
import org.springframework.boot.autoconfigure.SpringBootApplication;
2124
import org.springframework.context.annotation.Bean;
25+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
26+
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
27+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
2228

2329
/**
2430
* MCP Conformance Test Client - Spring HTTP Client Implementation.
@@ -42,13 +48,15 @@ public class ConformanceSpringClientApplication {
4248

4349
public static final String REGISTRATION_ID = "default_registration";
4450

51+
private final DefaultUrlValidator URL_VALIDATOR = new DefaultUrlValidator(true);
52+
4553
public static void main(String[] args) {
4654
SpringApplication.run(ConformanceSpringClientApplication.class, args);
4755
}
4856

4957
@Bean
5058
McpMetadataDiscoveryService discovery() {
51-
return new McpMetadataDiscoveryService();
59+
return new McpMetadataDiscoveryService(URL_VALIDATOR);
5260
}
5361

5462
@Bean
@@ -57,10 +65,24 @@ McpClientRegistrationRepository clientRegistrationRepository() {
5765
}
5866

5967
@Bean
60-
McpOAuth2ClientManager mcpOAuth2ClientManager(McpClientRegistrationRepository mcpClientRegistrationRepository,
68+
McpOAuth2DcrClientManager mcpOAuth2ClientManager(McpClientRegistrationRepository mcpClientRegistrationRepository,
6169
McpMetadataDiscoveryService mcpMetadataDiscoveryService) {
62-
return new DefaultMcpOAuth2ClientManager(mcpClientRegistrationRepository,
63-
new DynamicClientRegistrationService(), mcpMetadataDiscoveryService);
70+
return new DefaultMcpOAuth2DcrClientManager(mcpClientRegistrationRepository,
71+
new DynamicClientRegistrationService(URL_VALIDATOR), mcpMetadataDiscoveryService, URL_VALIDATOR);
72+
}
73+
74+
@Bean
75+
McpOAuth2CimdClientManager mcpOAuth2CimdClientManager(McpMetadataDiscoveryService mcpMetadataDiscoveryService,
76+
McpClientRegistrationRepository mcpClientRegistrationRepository) {
77+
return new DefaultMcpOAuth2CimdClientManager(mcpMetadataDiscoveryService, mcpClientRegistrationRepository,
78+
URL_VALIDATOR);
79+
}
80+
81+
@Bean
82+
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager(
83+
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
84+
McpClientRegistrationRepository clientRegistrationRepository) {
85+
return new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, oAuth2AuthorizedClientRepository);
6486
}
6587

6688
@Bean

conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/configuration/DefaultConfiguration.java

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,38 +4,65 @@
44

55
package io.modelcontextprotocol.conformance.client.configuration;
66

7-
import io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication;
7+
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
88
import io.modelcontextprotocol.conformance.client.scenario.DefaultScenario;
99
import org.springaicommunity.mcp.security.client.sync.config.McpClientOAuth2Configurer;
10+
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2CimdHttpClientTransportCustomizer;
11+
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2DcrHttpClientTransportCustomizer;
1012
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
11-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
13+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2DcrClientManager;
14+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.cimd.DefaultMcpOAuth2CimdClientManager;
15+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.cimd.McpOAuth2CimdClientManager;
1216

17+
import org.springframework.ai.mcp.customizer.McpClientCustomizer;
18+
import org.springframework.beans.factory.annotation.Value;
1319
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
1420
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
1521
import org.springframework.context.annotation.Bean;
1622
import org.springframework.context.annotation.Configuration;
17-
import org.springframework.security.config.Customizer;
1823
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
19-
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
24+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
25+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
2026
import org.springframework.security.web.SecurityFilterChain;
2127

2228
@Configuration
2329
@ConditionalOnExpression("#{environment['MCP_CONFORMANCE_SCENARIO'] != 'auth/pre-registration'}")
2430
public class DefaultConfiguration {
2531

32+
private final String TEST_CLIENT_ID_URL = "https://conformance-test.local/client-metadata.json";
33+
2634
@Bean
27-
DefaultScenario defaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
28-
ServletWebServerApplicationContext serverCtx,
29-
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
30-
McpOAuth2ClientManager mcpOAuth2ClientManager) {
31-
return new DefaultScenario(clientRegistrationRepository, serverCtx, oAuth2AuthorizedClientRepository,
32-
mcpOAuth2ClientManager);
35+
DefaultScenario defaultScenario(ServletWebServerApplicationContext serverCtx,
36+
McpClientCustomizer<HttpClientStreamableHttpTransport.Builder> transportCustomizer) {
37+
return new DefaultScenario(serverCtx, transportCustomizer);
38+
}
39+
40+
@Bean
41+
McpClientCustomizer<HttpClientStreamableHttpTransport.Builder> transportCustomizer(
42+
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager,
43+
McpClientRegistrationRepository clientRegistrationRepository,
44+
McpOAuth2DcrClientManager mcpOAuth2ClientManager, McpOAuth2CimdClientManager mcpOAuth2CimdClientManager,
45+
@Value("${mcp.conformance.scenario}") String scenario) {
46+
if (scenario.equals("auth/basic-cimd")) {
47+
if (mcpOAuth2CimdClientManager instanceof DefaultMcpOAuth2CimdClientManager mgr) {
48+
// Hardcode the client_id
49+
mgr.setClientRegistrationCustomizer(
50+
cr -> ClientRegistration.withClientRegistration(cr).clientId(TEST_CLIENT_ID_URL).build());
51+
}
52+
return new OAuth2CimdHttpClientTransportCustomizer(oAuth2AuthorizedClientManager,
53+
clientRegistrationRepository, mcpOAuth2CimdClientManager);
54+
55+
}
56+
else {
57+
return new OAuth2DcrHttpClientTransportCustomizer(oAuth2AuthorizedClientManager,
58+
clientRegistrationRepository, mcpOAuth2ClientManager);
59+
}
3360
}
3461

3562
@Bean
36-
SecurityFilterChain securityFilterChain(HttpSecurity http, ConformanceSpringClientApplication.ServerUrl serverUrl) {
63+
SecurityFilterChain securityFilterChain(HttpSecurity http) {
3764
return http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
38-
.with(new McpClientOAuth2Configurer(), Customizer.withDefaults())
65+
.with(new McpClientOAuth2Configurer(), mcp -> mcp.cimd(true))
3966
.build();
4067
}
4168

conformance-tests/client-spring-http-client/src/main/java/io/modelcontextprotocol/conformance/client/scenario/DefaultScenario.java

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,10 @@
1717
import org.slf4j.Logger;
1818
import org.slf4j.LoggerFactory;
1919
import org.springaicommunity.mcp.security.client.sync.AuthenticationMcpTransportContextProvider;
20-
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2HttpClientTransportCustomizer;
21-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
22-
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
2320

21+
import org.springframework.ai.mcp.customizer.McpClientCustomizer;
2422
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
2523
import org.springframework.http.client.JdkClientHttpRequestFactory;
26-
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
27-
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
2824
import org.springframework.web.client.RestClient;
2925
import org.springframework.web.util.UriComponentsBuilder;
3026

@@ -34,23 +30,14 @@ public class DefaultScenario implements Scenario {
3430

3531
private final ServletWebServerApplicationContext serverCtx;
3632

37-
private final DefaultOAuth2AuthorizedClientManager authorizedClientManager;
38-
39-
private final McpClientRegistrationRepository clientRegistrationRepository;
40-
41-
private final McpOAuth2ClientManager mcpOAuth2ClientManager;
33+
private final McpClientCustomizer<HttpClientStreamableHttpTransport.Builder> transportCustomizer;
4234

4335
private McpSyncClient client;
4436

45-
public DefaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
46-
ServletWebServerApplicationContext serverCtx,
47-
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
48-
McpOAuth2ClientManager mcpOAuth2ClientManager) {
37+
public DefaultScenario(ServletWebServerApplicationContext serverCtx,
38+
McpClientCustomizer<HttpClientStreamableHttpTransport.Builder> transportCustomizer) {
4939
this.serverCtx = serverCtx;
50-
this.clientRegistrationRepository = clientRegistrationRepository;
51-
this.mcpOAuth2ClientManager = mcpOAuth2ClientManager;
52-
this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository,
53-
oAuth2AuthorizedClientRepository);
40+
this.transportCustomizer = transportCustomizer;
5441
}
5542

5643
@Override
@@ -59,12 +46,10 @@ public void execute(String serverUrl) {
5946
var testServerUrl = "http://localhost:" + serverCtx.getWebServer().getPort();
6047
var testClient = buildTestClient(testServerUrl);
6148

62-
var customizer = new OAuth2HttpClientTransportCustomizer(authorizedClientManager, clientRegistrationRepository,
63-
mcpOAuth2ClientManager);
6449
var baseUri = UriComponentsBuilder.fromUriString(serverUrl).replacePath(null).toUriString();
6550
var path = UriComponentsBuilder.fromUriString(serverUrl).build().getPath();
6651
var transportBuilder = HttpClientStreamableHttpTransport.builder(baseUri).endpoint(path);
67-
customizer.customize("default-transport", transportBuilder);
52+
transportCustomizer.customize("default-transport", transportBuilder);
6853
HttpClientStreamableHttpTransport transport = transportBuilder.build();
6954

7055
this.client = McpClient.sync(transport)

conformance-tests/conformance-baseline.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,3 @@ client:
77
# - Client does not parse or respect retry: field timing
88
# - Client does not send Last-Event-ID header
99
- sse-retry
10-
# CIMD not implemented yet
11-
- auth/basic-cimd

0 commit comments

Comments
 (0)