CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-embabel-agent--embabel-agent-platform-autoconfigure

Spring Boot auto-configuration platform for Embabel Agent Framework, enabling annotation-driven profile activation and bootstrapping of agent configurations with MCP client support

Overview
Eval results
Files

implementation-details.mddocs/reference/

Implementation Details

Deep dive into how the auto-configuration package works internally.

Auto-Configuration Loading

Discovery Mechanism

File: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

com.embabel.agent.autoconfigure.platform.AgentPlatformAutoConfiguration

Process:

  1. Spring Boot scans classpath for AutoConfiguration.imports files
  2. Reads class names from file
  3. Applies auto-configuration filters
  4. Loads non-filtered configurations
  5. Processes @Import, @ImportAutoConfiguration annotations

SPI Registrations

File: 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.AgentPlatformAutoConfigurationFilter

Purpose:

  • EnvironmentPostProcessor: Early environment configuration
  • AutoConfigurationImportFilter: Filter unwanted auto-configurations

Initialization Order

Complete Startup Sequence

1. 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 ready

Order Constraints

QuiteMcpClientAutoConfiguration:

@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.

Component Scanning Details

Scanned Packages

@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
})

What Gets Scanned

  • @Component annotated classes
  • @Service annotated classes
  • @Repository annotated classes
  • @Controller / @RestController annotated classes
  • @Configuration annotated classes
  • Meta-annotated variants

Configuration Properties Scanning

@ConfigurationPropertiesScan(basePackages = {
    "com.embabel.agent.api",
    // ... same packages
})

Binds all @ConfigurationProperties classes in scanned packages.

Auto-Configuration Filter Implementation

AgentPlatformAutoConfigurationFilter

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:

  • Returns false for McpClientAutoConfiguration
  • Returns true for all other configurations
  • Spring Boot skips excluded configuration
  • QuiteMcpClientAutoConfiguration extends excluded class, providing resilient version

Environment Post-Processor Implementation

EnvironmentPostProcessor Details

Execution 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
    }
}

MCP Client Resilience Implementation

Error Handling in QuiteMcpClientAutoConfiguration

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:

  1. Try-catch around client creation
  2. Try-catch around initialization
  3. Errors logged but don't propagate
  4. Successfully initialized clients added to list
  5. Failed clients excluded
  6. Application continues with partial client list

Async clients: Similar logic with client.initialize().block().

Logging Implementation

Lifecycle Logging

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.");
}

Error Logging

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>

Conditional Activation

MCP Client Conditions

@ConditionalOnClass(McpSchema.class)  // MCP on classpath
@ConditionalOnProperty(
    prefix = "spring.ai.mcp.client",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true  // Enabled by default
)

Evaluation order:

  1. Check McpSchema.class on classpath
  2. Check spring.ai.mcp.client.enabled property
  3. If both true (or missing), activate configuration

Client Type Selection

@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.

Integration with Spring AI

Extending McpClientAutoConfiguration

public class QuiteMcpClientAutoConfiguration
    extends McpClientAutoConfiguration {
    // Inherits base configuration
    // Overrides bean methods with resilient versions
}

Why extend: Maintains compatibility with Spring AI while adding resilience.

Transport Integration

Transports provided by Spring AI:

  • StdioTransportAutoConfiguration
  • SseHttpClientTransportAutoConfiguration
  • StreamableHttpHttpClientTransportAutoConfiguration
  • SseWebFluxTransportAutoConfiguration
  • StreamableHttpWebFluxTransportAutoConfiguration

Integration point:

ObjectProvider<List<NamedClientMcpTransport>> transportsProvider

Injects all available transports from Spring AI auto-configurations.

Bean Creation Flow

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 injection

Performance Considerations

Component Scanning Impact

Scanned packages: 8 base packages Impact: Moderate startup time increase Optimization: Packages already specific to agent framework

MCP Client Initialization

With auto-initialization (initialized=true):

  • Startup time includes client initialization
  • Failures logged but don't block
  • Clients ready immediately

Without auto-initialization (initialized=false):

  • Faster startup
  • Manual initialization required
  • Delayed client availability

Memory Footprint

Component scanning: Registers beans in scanned packages MCP clients: One bean per configured transport Typical footprint: Minimal (< 1MB additional heap)

Thread Safety

All auto-configuration classes are thread-safe:

  • Configuration classes are singletons
  • Bean creation synchronized by Spring
  • MCP client lists immutable after creation

Extension Points

Custom Configurers

@Bean
public McpSyncClientConfigurer customConfigurer() {
    return (transportName, spec) -> {
        // Customize client creation
        return spec;
    };
}

Custom Handler Registries

@Component
public class MyHandlerRegistry implements ClientMcpSyncHandlersRegistry {
    // Implement handler methods
}

Custom Environment Post-Processors

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

docs

index.md

SCORING.md

tile.json