Spring Boot auto-configuration platform for Embabel Agent Framework, enabling annotation-driven profile activation and bootstrapping of agent configurations with MCP client support
Deep dive into how the auto-configuration package works internally.
File: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.embabel.agent.autoconfigure.platform.AgentPlatformAutoConfigurationProcess:
AutoConfiguration.imports files@Import, @ImportAutoConfiguration annotationsFile: META-INF/spring.factories
org.springframework.boot.env.EnvironmentPostProcessor=\
com.embabel.agent.config.annotation.spi.EnvironmentPostProcessor
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
com.embabel.agent.autoconfigure.platform.AgentPlatformAutoConfigurationFilterPurpose:
EnvironmentPostProcessor: Early environment configurationAutoConfigurationImportFilter: Filter unwanted auto-configurations1. SpringApplication constructor
2. Prepare environment
3. EnvironmentPostProcessor.postProcessEnvironment() [HIGHEST_PRECEDENCE]
└─ Sets embabel.agent.logging.personality from @EnableAgents
4. Other EnvironmentPostProcessors
5. Apply AutoConfigurationImportFilter
└─ AgentPlatformAutoConfigurationFilter excludes Spring AI default MCP config
6. Load auto-configurations
a. Transport auto-configurations (Spring AI)
b. QuiteMcpClientAutoConfiguration (afterName transports)
c. AgentPlatformAutoConfiguration
7. Process @Import annotations
└─ ScanConfiguration, AgentPlatformConfiguration, ToolGroupsConfiguration
8. Component scanning
└─ Scan com.embabel.agent.* packages
9. Create beans
10. Initialize beans
└─ MCP clients initialized with error resilience
11. Application readyQuiteMcpClientAutoConfiguration:
@AutoConfiguration(afterName = {
"StdioTransportAutoConfiguration",
"SseHttpClientTransportAutoConfiguration",
// ... other transports
})Ensures transports available before creating MCP clients.
EnvironmentPostProcessor:
int getOrder() { return Ordered.HIGHEST_PRECEDENCE; }Runs before all other post-processors to set properties early.
@ComponentScan(basePackages = {
"com.embabel.agent.api", // Core API interfaces
"com.embabel.agent.core", // Core implementations
"com.embabel.agent.experimental", // Experimental features
"com.embabel.agent.prompt", // Prompt templates
"com.embabel.agent.spi", // Service provider interfaces
"com.embabel.agent.test", // Test utilities
"com.embabel.agent.tools", // Agent tools
"com.embabel.agent.web" // Web components
})@Component annotated classes@Service annotated classes@Repository annotated classes@Controller / @RestController annotated classes@Configuration annotated classes@ConfigurationPropertiesScan(basePackages = {
"com.embabel.agent.api",
// ... same packages
})Binds all @ConfigurationProperties classes in scanned packages.
Purpose: Exclude Spring AI default MCP client auto-configuration.
Implementation:
public class AgentPlatformAutoConfigurationFilter
implements AutoConfigurationImportFilter {
private static final String EXCLUDED_CONFIG =
"org.springframework.ai.mcp.client.common.autoconfigure.McpClientAutoConfiguration";
public boolean[] match(String[] autoConfigurationClasses,
AutoConfigurationMetadata metadata) {
boolean[] matches = new boolean[autoConfigurationClasses.length];
for (int i = 0; i < autoConfigurationClasses.length; i++) {
if (autoConfigurationClasses[i] == null) {
matches[i] = false;
} else {
matches[i] = !autoConfigurationClasses[i].equals(EXCLUDED_CONFIG);
}
}
return matches;
}
}Effect:
false for McpClientAutoConfigurationtrue for all other configurationsQuiteMcpClientAutoConfiguration extends excluded class, providing resilient versionExecution timing: HIGHEST_PRECEDENCE (Integer.MIN_VALUE)
Processing logic:
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// Find @EnableAgents annotation
EnableAgents annotation = findEnableAgentsAnnotation(application);
if (annotation == null) {
return;
}
// Extract logging theme
String loggingTheme = annotation.loggingTheme();
// Set property if theme specified
if (loggingTheme != null && !loggingTheme.isEmpty()) {
MapPropertySource propertySource = new MapPropertySource(
"loggingThemeSource",
Collections.singletonMap(LOGGING_THEME_PROPERTY, loggingTheme)
);
// Add as highest priority property source
environment.getPropertySources().addFirst(propertySource);
}
// Windows console configuration (static initialization)
// Already executed in static block
}
private EnableAgents findEnableAgentsAnnotation(SpringApplication app) {
for (Object source : app.getAllSources()) {
if (source instanceof Class<?> clazz) {
EnableAgents annotation =
AnnotationUtils.findAnnotation(clazz, EnableAgents.class);
if (annotation != null) {
return annotation;
}
}
}
return null;
}Windows console configuration:
static {
if (WinUtils.IS_OS_WINDOWS()) {
WinUtils.CHCP_TO_UTF8(); // chcp 65001
WinUtils.SETUP_OPTIMAL_CONSOLE(); // Font optimization
}
}Sync clients:
List<McpSyncClient> mcpSyncClients(...) {
List<NamedClientMcpTransport> transports = transportsProvider.getIfAvailable();
List<McpSyncClient> clients = new ArrayList<>();
if (transports == null || transports.isEmpty()) {
return clients;
}
for (NamedClientMcpTransport transport : transports) {
try {
// Create client info
ClientInfo clientInfo = ClientInfo.builder()
.name(commonProperties.getName() + " - " + transport.name())
.version(commonProperties.getVersion())
.build();
// Build client spec
McpClient.SyncSpec spec = McpClient.sync()
.clientInfo(clientInfo)
.requestTimeout(commonProperties.getRequestTimeout())
.transport(transport.transport());
// Register handlers if available
ClientMcpSyncHandlersRegistry registry =
clientMcpSyncHandlersRegistry.getIfAvailable();
if (registry != null) {
spec = spec
.samplingHandler(request -> registry.handleSampling(transport.name(), request))
.elicitationHandler(request -> registry.handleElicitation(transport.name(), request))
// ... other handlers
;
}
// Apply custom configurer
spec = mcpSyncClientConfigurer.configure(transport.name(), spec);
// Build client
McpSyncClient client = spec.build();
// Initialize if enabled
if (commonProperties.isInitialized()) {
try {
client.initialize();
clients.add(client); // Success - add to list
} catch (Throwable t) {
logger.error(
"Failed to initialize MCP Sync Client: {} - " +
"Application startup will continue",
clientInfo.name(), t
);
// Client not added - app continues
}
} else {
clients.add(client); // Not initialized, add anyway
}
} catch (Exception e) {
logger.error("Error creating MCP client for transport: {}",
transport.name(), e);
}
}
return clients;
}Key resilience features:
Async clients: Similar logic with client.initialize().block().
AgentPlatformAutoConfiguration:
private static final Logger logger =
LoggerFactory.getLogger(AgentPlatformAutoConfiguration.class);
static {
logger.info("AgentPlatformAutoConfiguration has been initialized.");
}
@PostConstruct
public void logEvent() {
logger.info("AgentPlatformAutoConfiguration about to be processed...");
}ScanConfiguration:
static {
logger.info("ComponentConfiguration initialized: Scanning com.embabel.agent packages.");
}MCP client failures:
ERROR c.e.a.a.p.QuiteMcpClientAutoConfiguration -
Failed to initialize MCP Sync Client: my-agent - filesystem
- Application startup will continue
<stack trace>@ConditionalOnClass(McpSchema.class) // MCP on classpath
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = "enabled",
havingValue = "true",
matchIfMissing = true // Enabled by default
)Evaluation order:
McpSchema.class on classpathspring.ai.mcp.client.enabled property@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = "type",
havingValue = "SYNC",
matchIfMissing = true // SYNC by default
)
List<McpSyncClient> mcpSyncClients(...)
@ConditionalOnProperty(
prefix = "spring.ai.mcp.client",
name = "type",
havingValue = "ASYNC"
)
List<McpAsyncClient> mcpAsyncClients(...)Only one bean type created based on type property.
public class QuiteMcpClientAutoConfiguration
extends McpClientAutoConfiguration {
// Inherits base configuration
// Overrides bean methods with resilient versions
}Why extend: Maintains compatibility with Spring AI while adding resilience.
Transports provided by Spring AI:
StdioTransportAutoConfigurationSseHttpClientTransportAutoConfigurationStreamableHttpHttpClientTransportAutoConfigurationSseWebFluxTransportAutoConfigurationStreamableHttpWebFluxTransportAutoConfigurationIntegration point:
ObjectProvider<List<NamedClientMcpTransport>> transportsProviderInjects all available transports from Spring AI auto-configurations.
1. AgentPlatformAutoConfiguration loaded
└─ Imports QuiteMcpClientAutoConfiguration
└─ Imports ScanConfiguration, AgentPlatformConfiguration, ToolGroupsConfiguration
2. QuiteMcpClientAutoConfiguration activated
└─ Conditions evaluated (McpSchema, enabled property)
└─ Bean method selected (SYNC vs ASYNC)
3. MCP client bean created
└─ Transports injected
└─ For each transport:
├─ Build client spec
├─ Apply configurer
├─ Create client
└─ Initialize (with error handling)
4. ScanConfiguration processed
└─ Component scan executed
└─ Configuration properties scan executed
5. Imported configurations processed
└─ AgentPlatformConfiguration beans created
└─ ToolGroupsConfiguration beans created
6. All beans available for injectionScanned packages: 8 base packages Impact: Moderate startup time increase Optimization: Packages already specific to agent framework
With auto-initialization (initialized=true):
Without auto-initialization (initialized=false):
Component scanning: Registers beans in scanned packages MCP clients: One bean per configured transport Typical footprint: Minimal (< 1MB additional heap)
All auto-configuration classes are thread-safe:
@Bean
public McpSyncClientConfigurer customConfigurer() {
return (transportName, spec) -> {
// Customize client creation
return spec;
};
}@Component
public class MyHandlerRegistry implements ClientMcpSyncHandlersRegistry {
// Implement handler methods
}Add your own with different order values:
public class MyPostProcessor implements EnvironmentPostProcessor {
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1; // After agent post-processor
}
}Install with Tessl CLI
npx tessl i tessl/maven-com-embabel-agent--embabel-agent-platform-autoconfigure@0.3.0