conformance: update to mcp-security 0.1.5, pass scope-step-up

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
Daniel Garnier-Moiroux 2026-03-31 16:32:11 +02:00 • committed by Daniel Garnier-Moiroux
commit 22e7bd4b56
5 changed files with 86 additions and 46 deletions

View file

@ -8,8 +8,11 @@ import java.util.Optional;
import io.modelcontextprotocol.conformance.client.scenario.Scenario;
import org.springaicommunity.mcp.security.client.sync.oauth2.metadata.McpMetadataDiscoveryService;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DefaultMcpOAuth2ClientManager;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.DynamicClientRegistrationService;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.InMemoryMcpClientRegistrationRepository;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
@ -49,8 +52,15 @@ public class ConformanceSpringClientApplication {
}
@Bean
InMemoryMcpClientRegistrationRepository clientRegistrationRepository(McpMetadataDiscoveryService discovery) {
return new InMemoryMcpClientRegistrationRepository(new DynamicClientRegistrationService(), discovery);
McpClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryMcpClientRegistrationRepository();
}
@Bean
McpOAuth2ClientManager mcpOAuth2ClientManager(McpClientRegistrationRepository mcpClientRegistrationRepository,
McpMetadataDiscoveryService mcpMetadataDiscoveryService) {
return new DefaultMcpOAuth2ClientManager(mcpClientRegistrationRepository,
new DynamicClientRegistrationService(), mcpMetadataDiscoveryService);
}
@Bean

View file

@ -5,6 +5,7 @@
package io.modelcontextprotocol.conformance.client;
import io.modelcontextprotocol.conformance.client.scenario.Scenario;
import io.modelcontextprotocol.spec.McpSchema;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@ -27,4 +28,15 @@ class McpClientController {
return "OK";
}
@GetMapping("/tools-list")
public String toolsList() {
return "OK";
}
@GetMapping("/tools-call")
public String toolsCall() {
this.scenario.getMcpClient().callTool(McpSchema.CallToolRequest.builder().name("test-tool").build());
return "OK";
}
}

View file

@ -8,15 +8,16 @@ import io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplica
import io.modelcontextprotocol.conformance.client.scenario.DefaultScenario;
import org.springaicommunity.mcp.security.client.sync.config.McpClientOAuth2Configurer;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.web.SecurityFilterChain;
import static io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication.REGISTRATION_ID;
@Configuration
@ConditionalOnExpression("#{environment['MCP_CONFORMANCE_SCENARIO'] != 'auth/pre-registration'}")
@ -25,15 +26,16 @@ public class DefaultConfiguration {
@Bean
DefaultScenario defaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
ServletWebServerApplicationContext serverCtx,
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository) {
return new DefaultScenario(clientRegistrationRepository, serverCtx, oAuth2AuthorizedClientRepository);
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
McpOAuth2ClientManager mcpOAuth2ClientManager) {
return new DefaultScenario(clientRegistrationRepository, serverCtx, oAuth2AuthorizedClientRepository,
mcpOAuth2ClientManager);
}
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http, ConformanceSpringClientApplication.ServerUrl serverUrl) {
return http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll())
.with(new McpClientOAuth2Configurer(),
mcp -> mcp.registerMcpOAuth2Client(REGISTRATION_ID, serverUrl.value()))
.with(new McpClientOAuth2Configurer(), Customizer.withDefaults())
.build();
}

View file

@ -17,15 +17,16 @@ import org.jspecify.annotations.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springaicommunity.mcp.security.client.sync.AuthenticationMcpTransportContextProvider;
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2AuthorizationCodeSyncHttpRequestCustomizer;
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2HttpClientTransportCustomizer;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpOAuth2ClientManager;
import org.springframework.boot.web.server.servlet.context.ServletWebServerApplicationContext;
import org.springframework.http.client.JdkClientHttpRequestFactory;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.web.client.RestClient;
import static io.modelcontextprotocol.conformance.client.ConformanceSpringClientApplication.REGISTRATION_ID;
import org.springframework.web.util.UriComponentsBuilder;
public class DefaultScenario implements Scenario {
@ -35,12 +36,19 @@ public class DefaultScenario implements Scenario {
private final DefaultOAuth2AuthorizedClientManager authorizedClientManager;
private final McpClientRegistrationRepository clientRegistrationRepository;
private final McpOAuth2ClientManager mcpOAuth2ClientManager;
private McpSyncClient client;
public DefaultScenario(McpClientRegistrationRepository clientRegistrationRepository,
ServletWebServerApplicationContext serverCtx,
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository) {
OAuth2AuthorizedClientRepository oAuth2AuthorizedClientRepository,
McpOAuth2ClientManager mcpOAuth2ClientManager) {
this.serverCtx = serverCtx;
this.clientRegistrationRepository = clientRegistrationRepository;
this.mcpOAuth2ClientManager = mcpOAuth2ClientManager;
this.authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository,
oAuth2AuthorizedClientRepository);
}
@ -51,10 +59,13 @@ public class DefaultScenario implements Scenario {
var testServerUrl = "http://localhost:" + serverCtx.getWebServer().getPort();
var testClient = buildTestClient(testServerUrl);
var customizer = new OAuth2AuthorizationCodeSyncHttpRequestCustomizer(authorizedClientManager, REGISTRATION_ID);
HttpClientStreamableHttpTransport transport = HttpClientStreamableHttpTransport.builder(serverUrl)
.httpRequestCustomizer(customizer)
.build();
var customizer = new OAuth2HttpClientTransportCustomizer(authorizedClientManager, clientRegistrationRepository,
mcpOAuth2ClientManager);
var baseUri = UriComponentsBuilder.fromUriString(serverUrl).replacePath(null).toUriString();
var path = UriComponentsBuilder.fromUriString(serverUrl).build().getPath();
var transportBuilder = HttpClientStreamableHttpTransport.builder(baseUri).endpoint(path);
customizer.customize("default-transport", transportBuilder);
HttpClientStreamableHttpTransport transport = transportBuilder.build();
this.client = McpClient.sync(transport)
.transportContextProvider(new AuthenticationMcpTransportContextProvider())
@ -64,6 +75,8 @@ public class DefaultScenario implements Scenario {
try {
testClient.get().uri("/initialize-mcp-client").retrieve().toBodilessEntity();
testClient.get().uri("/tools-list").retrieve().toBodilessEntity();
testClient.get().uri("/tools-call").retrieve().toBodilessEntity();
}
finally {
// Close the client (which will close the transport)

View file

@ -87,7 +87,7 @@ public class PreRegistrationScenario implements Scenario {
.clientSecret(oauthCredentials.clientSecret())
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
clientRegistrationRepository.addPreRegisteredClient(registration,
clientRegistrationRepository.addClientRegistration(registration,
metadata.protectedResourceMetadata().resource());
}