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
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2026-2026 the original author or authors.
*/

package io.modelcontextprotocol.conformance.client.condition;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.context.annotation.Conditional;

/**
* Condition to include beans only when certain scenarios are active / inactive. Checks
* the value of the {@code MCP_CONFORMANCE_SCENARIO} environment variable and matches
* against {@link #included()} and {@link #excluded()}. Exactly one of these attributes
* must be defined.
* <p>
* Usage: <pre>
*
* &#64;Configuration
* &#64;ConditionalOnScenario(excluded =
* {
* "auth/pre-registration",
* "auth/client-credentials-basic"
* }
* )
* public class DefaultConfiguration {
* // ...
* }
* </pre>
*
* @author Daniel Garnier-Moiroux
* @see OnScenarioCondition
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnScenarioCondition.class)
public @interface ConditionalOnScenario {

String[] included() default {};

String[] excluded() default {};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright 2026-2026 the original author or authors.
*/

package io.modelcontextprotocol.conformance.client.condition;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.jspecify.annotations.Nullable;

import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.Assert;

/**
* Condition implementation for {@link ConditionalOnScenario}.
*
* @author Daniel Garnier-Moiroux
*/
class OnScenarioCondition extends SpringBootCondition {

private static final String ENV_VAR = "MCP_CONFORMANCE_SCENARIO";

@Override
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
Map<String, @Nullable Object> attributes = metadata
.getAnnotationAttributes(ConditionalOnScenario.class.getName());
Assert.state(attributes != null, "'attributes' must not be null");

String[] included = (String[]) attributes.get("included");
String[] excluded = (String[]) attributes.get("excluded");

boolean hasIncluded = included != null && included.length > 0;
boolean hasExcluded = excluded != null && excluded.length > 0;

Assert.state(hasIncluded ^ hasExcluded,
"@ConditionalOnScenario must have exactly one of 'included' or 'excluded' defined");

String scenario = System.getenv(ENV_VAR);

if (hasIncluded) {
List<String> includedList = Arrays.asList(included);
boolean matches = scenario != null && includedList.contains(scenario);
ConditionMessage message = ConditionMessage.forCondition(ConditionalOnScenario.class)
.because("scenario '" + scenario + "' " + (matches ? "is" : "is not") + " in included list "
+ includedList);
return matches ? ConditionOutcome.match(message) : ConditionOutcome.noMatch(message);
}
else {
List<String> excludedList = Arrays.asList(excluded);
boolean matches = scenario == null || !excludedList.contains(scenario);
ConditionMessage message = ConditionMessage.forCondition(ConditionalOnScenario.class)
.because("scenario '" + scenario + "' " + (matches ? "is not" : "is") + " in excluded list "
+ excludedList);
return matches ? ConditionOutcome.match(message) : ConditionOutcome.noMatch(message);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package io.modelcontextprotocol.conformance.client.configuration;

import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
import io.modelcontextprotocol.conformance.client.condition.ConditionalOnScenario;
import io.modelcontextprotocol.conformance.client.scenario.DefaultScenario;
import org.springaicommunity.mcp.security.client.sync.config.McpClientOAuth2Configurer;
import org.springaicommunity.mcp.security.client.sync.oauth2.http.client.OAuth2CimdHttpClientTransportCustomizer;
Expand All @@ -16,7 +17,6 @@

import org.springframework.ai.mcp.customizer.McpClientCustomizer;
import org.springframework.beans.factory.annotation.Value;
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;
Expand All @@ -26,7 +26,7 @@
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@ConditionalOnExpression("#{environment['MCP_CONFORMANCE_SCENARIO'] != 'auth/pre-registration'}")
@ConditionalOnScenario(excluded = { "auth/pre-registration", "auth/client-credentials-basic" })
public class DefaultConfiguration {

private final String TEST_CLIENT_ID_URL = "https://conformance-test.local/client-metadata.json";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

package io.modelcontextprotocol.conformance.client.configuration;

import io.modelcontextprotocol.conformance.client.condition.ConditionalOnScenario;
import io.modelcontextprotocol.conformance.client.scenario.PreRegistrationScenario;
import org.springaicommunity.mcp.security.client.sync.config.McpClientOAuth2Configurer;
import org.springaicommunity.mcp.security.client.sync.oauth2.metadata.McpMetadataDiscoveryService;
import org.springaicommunity.mcp.security.client.sync.oauth2.registration.McpClientRegistrationRepository;

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
Expand All @@ -18,7 +18,7 @@
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@ConditionalOnProperty(name = "mcp.conformance.scenario", havingValue = "auth/pre-registration")
@ConditionalOnScenario(included = { "auth/pre-registration", "auth/client-credentials-basic" })
public class PreRegistrationConfiguration {

@Bean
Expand Down
Loading