Skip to content

Commit 22e7bd4

Browse files
committed
conformance: update to mcp-security 0.1.5, pass scope-step-up
Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent b6eb672 commit 22e7bd4

File tree

9 files changed

+86
-46
lines changed

9 files changed

+86
-46
lines changed

conformance-tests/VALIDATION_RESULTS.md

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
**Server Tests:** 40/40 passed (100%)
66
**Client Tests:** 3/4 scenarios passed (9/10 checks passed)
7-
**Auth Tests:** 12/14 scenarios fully passing (178 passed, 1 failed, 1 warning, 85.7% scenarios, 98.9% checks)
7+
**Auth Tests:** 14/15 scenarios fully passing (196 passed, 0 failed, 1 warning, 93.3% scenarios, 99.5% checks)
88

99
## Server Test Results
1010

@@ -37,35 +37,35 @@
3737

3838
## Auth Test Results (Spring HTTP Client)
3939

40-
**Status: 178 passed, 1 failed, 1 warning across 14 scenarios**
40+
**Status: 196 passed, 0 failed, 1 warning across 15 scenarios**
4141

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

44-
### Fully Passing (12/14 scenarios)
44+
### Fully Passing (14/15 scenarios)
4545

46-
- **auth/metadata-default (12/12):** Default metadata discovery
47-
- **auth/metadata-var1 (12/12):** Metadata discovery variant 1
48-
- **auth/metadata-var2 (12/12):** Metadata discovery variant 2
49-
- **auth/metadata-var3 (12/12):** Metadata discovery variant 3
50-
- **auth/scope-from-www-authenticate (13/13):** Scope extraction from WWW-Authenticate header
51-
- **auth/scope-from-scopes-supported (13/13):** Scope extraction from scopes_supported
52-
- **auth/scope-omitted-when-undefined (13/13):** Scope omitted when not defined
46+
- **auth/metadata-default (13/13):** Default metadata discovery
47+
- **auth/metadata-var1 (13/13):** Metadata discovery variant 1
48+
- **auth/metadata-var2 (13/13):** Metadata discovery variant 2
49+
- **auth/metadata-var3 (13/13):** Metadata discovery variant 3
50+
- **auth/scope-from-www-authenticate (14/14):** Scope extraction from WWW-Authenticate header
51+
- **auth/scope-from-scopes-supported (14/14):** Scope extraction from scopes_supported
52+
- **auth/scope-omitted-when-undefined (14/14):** Scope omitted when not defined
53+
- **auth/scope-step-up (16/16):** Scope step-up challenge
5354
- **auth/scope-retry-limit (11/11):** Scope retry limit handling
54-
- **auth/token-endpoint-auth-basic (17/17):** Token endpoint with HTTP Basic auth
55-
- **auth/token-endpoint-auth-post (17/17):** Token endpoint with POST body auth
56-
- **auth/token-endpoint-auth-none (17/17):** Token endpoint with no client auth
55+
- **auth/token-endpoint-auth-basic (18/18):** Token endpoint with HTTP Basic auth
56+
- **auth/token-endpoint-auth-post (18/18):** Token endpoint with POST body auth
57+
- **auth/token-endpoint-auth-none (18/18):** Token endpoint with no client auth
58+
- **auth/resource-mismatch (2/2):** Resource mismatch handling
5759
- **auth/pre-registration (6/6):** Pre-registered client credentials flow
5860

59-
### Partially Passing (2/14 scenarios)
61+
### Partially Passing (1/15 scenarios)
6062

61-
- **auth/basic-cimd (12/12 + 1 warning):** Basic Client-Initiated Metadata Discovery — all checks pass, minor warning
62-
- **auth/scope-step-up (11/12):** Scope step-up challenge — 1 failure, client does not fully handle scope escalation after initial authorization
63+
- **auth/basic-cimd (13/13 + 1 warning):** Basic Client-Initiated Metadata Discovery — all checks pass, minor warning
6364

6465
## Known Limitations
6566

6667
1. **Client SSE Retry:** Client doesn't parse or respect the `retry:` field, reconnects immediately, and doesn't send Last-Event-ID header
67-
2. **Auth Scope Step-Up:** Client does not fully handle scope step-up challenges where the server requests additional scopes after initial authorization
68-
3. **Auth Basic CIMD:** Minor conformance warning in the basic Client-Initiated Metadata Discovery flow
68+
2. **Auth Basic CIMD:** Minor conformance warning in the basic Client-Initiated Metadata Discovery flow
6969

7070
## Running Tests
7171

@@ -113,4 +113,3 @@ npx @modelcontextprotocol/conformance@0.1.15 client \
113113
### High Priority
114114
1. Fix client SSE retry field handling in `HttpClientStreamableHttpTransport`
115115
2. Implement CIMD
116-
3. Implement scope step up

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Test with @modelcontextprotocol/conformance@0.1.15.
2626
| auth/scope-from-www-authenticate | ✅ Pass | 13/13 |
2727
| auth/scope-from-scopes-supported | ✅ Pass | 13/13 |
2828
| auth/scope-omitted-when-undefined | ✅ Pass | 13/13 |
29-
| auth/scope-step-up | ❌ Fail | 11/12 (1 failed) |
29+
| auth/scope-step-up | ✅ Pass | 12/12 |
3030
| auth/scope-retry-limit | ✅ Pass | 11/11 |
3131
| auth/token-endpoint-auth-basic | ✅ Pass | 17/17 |
3232
| auth/token-endpoint-auth-post | ✅ Pass | 17/17 |
@@ -67,7 +67,7 @@ cd conformance-tests/client-spring-http-client
6767

6868
This creates an executable JAR at:
6969
```
70-
target/client-spring-http-client-1.1.0-SNAPSHOT.jar
70+
target/client-spring-http-client-2.0.0-SNAPSHOT.jar
7171
```
7272

7373
## Running Tests
@@ -79,7 +79,7 @@ Run the full auth suite:
7979
```bash
8080
npx @modelcontextprotocol/conformance@0.1.15 client \
8181
--spec-version 2025-11-25 \
82-
--command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.0-SNAPSHOT.jar" \
82+
--command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-2.0.0-SNAPSHOT.jar" \
8383
--suite auth
8484
```
8585

@@ -88,7 +88,7 @@ Run a single scenario:
8888
```bash
8989
npx @modelcontextprotocol/conformance@0.1.15 client \
9090
--spec-version 2025-11-25 \
91-
--command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.0-SNAPSHOT.jar" \
91+
--command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-2.0.0-SNAPSHOT.jar" \
9292
--scenario auth/metadata-default
9393
```
9494

@@ -97,7 +97,7 @@ Run with verbose output:
9797
```bash
9898
npx @modelcontextprotocol/conformance@0.1.15 client \
9999
--spec-version 2025-11-25 \
100-
--command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.0-SNAPSHOT.jar" \
100+
--command "java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-2.0.0-SNAPSHOT.jar" \
101101
--scenario auth/metadata-default \
102102
--verbose
103103
```
@@ -108,7 +108,7 @@ You can also run the client manually if you have a test server:
108108

109109
```bash
110110
export MCP_CONFORMANCE_SCENARIO=auth/metadata-default
111-
java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-1.1.0-SNAPSHOT.jar http://localhost:3000/mcp
111+
java -jar conformance-tests/client-spring-http-client/target/client-spring-http-client-2.0.0-SNAPSHOT.jar http://localhost:3000/mcp
112112
```
113113

114114
## Known Issues

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222

2323
<properties>
2424
<java.version>17</java.version>
25-
<spring-boot.version>4.0.2</spring-boot.version>
26-
<spring-ai.version>2.0.0-M2</spring-ai.version>
25+
<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>
2728
<maven.deploy.skip>true</maven.deploy.skip>
2829
</properties>
2930

@@ -64,7 +65,12 @@
6465
<dependency>
6566
<groupId>org.springaicommunity</groupId>
6667
<artifactId>mcp-client-security</artifactId>
67-
<version>0.1.2</version>
68+
<version>${spring-ai-mcp-security.version}</version>
69+
</dependency>
70+
<dependency>
71+
<groupId>io.modelcontextprotocol.sdk</groupId>
72+
<artifactId>mcp-core</artifactId>
73+
<version>${project.version}</version>
6874
</dependency>
6975
</dependencies>
7076

@@ -106,4 +112,4 @@
106112
</repository>
107113
</repositories>
108114

109-
</project>
115+
</project>

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
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;
1112
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DynamicClientRegistrationService;
1213
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.InMemoryMcpClientRegistrationRepository;
14+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
15+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
1316

1417
import org.springframework.boot.ApplicationArguments;
1518
import org.springframework.boot.ApplicationRunner;
@@ -49,8 +52,15 @@ McpMetadataDiscoveryService discovery() {
4952
}
5053

5154
@Bean
52-
InMemoryMcpClientRegistrationRepository clientRegistrationRepository(McpMetadataDiscoveryService discovery) {
53-
return new InMemoryMcpClientRegistrationRepository(new DynamicClientRegistrationService(), discovery);
55+
McpClientRegistrationRepository clientRegistrationRepository() {
56+
return new InMemoryMcpClientRegistrationRepository();
57+
}
58+
59+
@Bean
60+
McpOAuth2ClientManager mcpOAuth2ClientManager(McpClientRegistrationRepository mcpClientRegistrationRepository,
61+
McpMetadataDiscoveryService mcpMetadataDiscoveryService) {
62+
return new DefaultMcpOAuth2ClientManager(mcpClientRegistrationRepository,
63+
new DynamicClientRegistrationService(), mcpMetadataDiscoveryService);
5464
}
5565

5666
@Bean

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package io.modelcontextprotocol.conformance.client;
66

77
import io.modelcontextprotocol.conformance.client.scenario.Scenario;
8+
import io.modelcontextprotocol.spec.McpSchema;
89

910
import org.springframework.web.bind.annotation.GetMapping;
1011
import org.springframework.web.bind.annotation.RestController;
@@ -27,4 +28,15 @@ public String execute() {
2728
return "OK";
2829
}
2930

31+
@GetMapping("/tools-list")
32+
public String toolsList() {
33+
return "OK";
34+
}
35+
36+
@GetMapping("/tools-call")
37+
public String toolsCall() {
38+
this.scenario.getMcpClient().callTool(McpSchema.CallToolRequest.builder().name("test-tool").build());
39+
return "OK";
40+
}
41+
3042
}

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88
import io.modelcontextprotocol.conformance.client.scenario.DefaultScenario;
99
import org.springaicommunity.mcp.security.client.sync.config.McpClientOAuth2Configurer;
1010
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
11+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
1112

1213
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
1314
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
1415
import org.springframework.context.annotation.Bean;
1516
import org.springframework.context.annotation.Configuration;
17+
import org.springframework.security.config.Customizer;
1618
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
1719
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
1820
import org.springframework.security.web.SecurityFilterChain;
19-
import static io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication.REGISTRATION_ID;
2021

2122
@Configuration
2223
@ConditionalOnExpression("#{environment['MCP_CONFORMANCE_SCENARIO'] != 'auth/pre-registration'}")
@@ -25,15 +26,16 @@ public class DefaultConfiguration {
2526
@Bean
2627
DefaultScenario defaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
2728
ServletWebServerApplicationContext serverCtx,
28-
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository) {
29-
return new DefaultScenario(clientRegistrationRepository, serverCtx, oAuth2AuthorizedClientRepository);
29+
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
30+
McpOAuth2ClientManager mcpOAuth2ClientManager) {
31+
return new DefaultScenario(clientRegistrationRepository, serverCtx, oAuth2AuthorizedClientRepository,
32+
mcpOAuth2ClientManager);
3033
}
3134

3235
@Bean
3336
SecurityFilterChain securityFilterChain(HttpSecurity http, ConformanceSpringClientApplication.ServerUrl serverUrl) {
3437
return http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
35-
.with(new McpClientOAuth2Configurer(),
36-
mcp -> mcp.registerMcpOAuth2Client(REGISTRATION_ID, serverUrl.value()))
38+
.with(new McpClientOAuth2Configurer(), Customizer.withDefaults())
3739
.build();
3840
}
3941

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

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
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.OAuth2AuthorizationCodeSyncHttpRequestCustomizer;
20+
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2HttpClientTransportCustomizer;
2121
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
22+
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
2223

2324
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
2425
import org.springframework.http.client.JdkClientHttpRequestFactory;
2526
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
2627
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
2728
import org.springframework.web.client.RestClient;
28-
import static io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication.REGISTRATION_ID;
29+
import org.springframework.web.util.UriComponentsBuilder;
2930

3031
public class DefaultScenario implements Scenario {
3132

@@ -35,12 +36,19 @@ public class DefaultScenario implements Scenario {
3536

3637
private final DefaultOAuth2AuthorizedClientManager authorizedClientManager;
3738

39+
private final McpClientRegistrationRepository clientRegistrationRepository;
40+
41+
private final McpOAuth2ClientManager mcpOAuth2ClientManager;
42+
3843
private McpSyncClient client;
3944

4045
public DefaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
4146
ServletWebServerApplicationContext serverCtx,
42-
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository) {
47+
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
48+
McpOAuth2ClientManager mcpOAuth2ClientManager) {
4349
this.serverCtx = serverCtx;
50+
this.clientRegistrationRepository = clientRegistrationRepository;
51+
this.mcpOAuth2ClientManager = mcpOAuth2ClientManager;
4452
this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository,
4553
oAuth2AuthorizedClientRepository);
4654
}
@@ -51,10 +59,13 @@ public void execute(String serverUrl) {
5159
var testServerUrl = "http://localhost:" + serverCtx.getWebServer().getPort();
5260
var testClient = buildTestClient(testServerUrl);
5361

54-
var customizer = new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(authorizedClientManager, REGISTRATION_ID);
55-
HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(serverUrl)
56-
.httpRequestCustomizer(customizer)
57-
.build();
62+
var customizer = new OAuth2HttpClientTransportCustomizer(authorizedClientManager, clientRegistrationRepository,
63+
mcpOAuth2ClientManager);
64+
var baseUri = UriComponentsBuilder.fromUriString(serverUrl).replacePath(null).toUriString();
65+
var path = UriComponentsBuilder.fromUriString(serverUrl).build().getPath();
66+
var transportBuilder = HttpClientStreamableHttpTransport.builder(baseUri).endpoint(path);
67+
customizer.customize("default-transport", transportBuilder);
68+
HttpClientStreamableHttpTransport transport = transportBuilder.build();
5869

5970
this.client = McpClient.sync(transport)
6071
.transportContextProvider(new AuthenticationMcpTransportContextProvider())
@@ -64,6 +75,8 @@ public void execute(String serverUrl) {
6475

6576
try {
6677
testClient.get().uri("/initialize-mcp-client").retrieve().toBodilessEntity();
78+
testClient.get().uri("/tools-list").retrieve().toBodilessEntity();
79+
testClient.get().uri("/tools-call").retrieve().toBodilessEntity();
6780
}
6881
finally {
6982
// Close the client (which will close the transport)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private void setClientRegistration(String mcpServerUrl, PreRegistrationContext o
8787
.clientSecret(oauthCredentials.clientSecret())
8888
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
8989
.build();
90-
clientRegistrationRepository.addPreRegisteredClient(registration,
90+
clientRegistrationRepository.addClientRegistration(registration,
9191
metadata.protectedResourceMetadata().resource());
9292
}
9393

conformance-tests/conformance-baseline.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,3 @@ client:
99
- sse-retry
1010
# CIMD not implemented yet
1111
- auth/basic-cimd
12-
# Scope step up beyond initial authorization request not implemented
13-
- auth/scope-step-up

0 commit comments

Comments
 (0)