Discover and Export available Agent(s) as MCP Servers
API reference for MCP server auto-configuration classes, configuration beans, and conditional annotations.
com.embabel.agent.mcpserver.config // Configuration classes
com.embabel.agent.mcpserver.sync.config // Sync config
com.embabel.agent.mcpserver.async.config // Async configThe library uses Spring Boot auto-configuration to automatically set up the MCP server based on the spring.ai.mcp.server.type property. All configuration is annotation-driven with sensible defaults.
Base configuration class providing common functionality.
package com.embabel.agent.mcpserver.config
abstract class AbstractMcpServerConfiguration {
protected abstract fun createServerInfo(
applicationName: String,
executionMode: McpExecutionMode
): ServerInfo
protected abstract fun createToolRegistry(): ToolRegistry
protected fun getApplicationName(environment: Environment): String {
return environment.getProperty("spring.application.name", "agent-api")
}
}Common Behavior:
Auto-configuration for synchronous mode.
package com.embabel.agent.mcpserver.sync.config
@Configuration
@ConditionalOnProperty(
value = ["spring.ai.mcp.server.type"],
havingValue = "SYNC",
matchIfMissing = true
)
class McpSyncServerConfiguration : AbstractMcpServerConfiguration() {
@Bean
fun syncServerInfo(environment: Environment): ServerInfo {
return createServerInfo(
applicationName = getApplicationName(environment),
executionMode = McpExecutionMode.SYNC
)
}
@Bean
fun syncToolRegistry(): ToolRegistry {
return createToolRegistry()
}
@Bean
fun mcpSyncServer(
toolRegistry: ToolRegistry,
resourcePublishers: List<McpResourcePublisher>,
promptPublishers: List<McpPromptPublisher>
): McpSyncServer {
// Create and configure sync server
}
@Bean
fun syncServerStrategy(
mcpSyncServer: McpSyncServer,
toolRegistry: ToolRegistry,
serverInfo: ServerInfo
): McpServerStrategy {
return SyncServerStrategy(mcpSyncServer, toolRegistry, serverInfo)
}
}Activated When:
spring.ai.mcp.server.type=SYNC, orProvides Beans:
ServerInfo - Server metadataToolRegistry - Tool registry implementationMcpSyncServer - Synchronous MCP serverMcpServerStrategy - Sync server strategyUsage:
# application.properties
spring.ai.mcp.server.type=SYNC
spring.application.name=my-serviceAuto-configuration for asynchronous mode.
package com.embabel.agent.mcpserver.async.config
@Configuration
@ConditionalOnProperty(
value = ["spring.ai.mcp.server.type"],
havingValue = "ASYNC"
)
class McpAsyncServerConfiguration : AbstractMcpServerConfiguration() {
@Bean
fun asyncServerInfo(environment: Environment): ServerInfo {
return createServerInfo(
applicationName = getApplicationName(environment),
executionMode = McpExecutionMode.ASYNC
)
}
@Bean
fun asyncToolRegistry(): ToolRegistry {
return createToolRegistry()
}
@Bean
fun mcpAsyncServer(
toolRegistry: ToolRegistry,
resourcePublishers: List<McpAsyncResourcePublisher>,
promptPublishers: List<McpAsyncPromptPublisher>
): McpAsyncServer {
// Create and configure async server
}
@Bean
fun asyncServerStrategy(
mcpAsyncServer: McpAsyncServer,
toolRegistry: ToolRegistry,
serverInfo: ServerInfo
): McpServerStrategy {
return AsyncServerStrategy(mcpAsyncServer, toolRegistry, serverInfo)
}
}Activated When:
spring.ai.mcp.server.type=ASYNCProvides Beans:
ServerInfo - Server metadataToolRegistry - Tool registry implementationMcpAsyncServer - Asynchronous MCP serverMcpServerStrategy - Async server strategyUsage:
# application.properties
spring.ai.mcp.server.type=ASYNC
spring.application.name=my-service# Execution mode (SYNC or ASYNC)
spring.ai.mcp.server.type=SYNC
# Application name
spring.application.name=my-agent-apiCreate type-safe configuration properties:
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
@Configuration
@ConfigurationProperties(prefix = "mcp.server")
data class McpServerConfig(
var maxTools: Int = 100,
var maxResources: Int = 50,
var enableMetrics: Boolean = true,
var timeoutSeconds: Long = 30,
var features: FeatureFlags = FeatureFlags()
)
data class FeatureFlags(
var experimental: Boolean = false,
var analytics: Boolean = true,
var debugTools: Boolean = false
)Properties File:
# application.properties
mcp.server.max-tools=200
mcp.server.max-resources=100
mcp.server.enable-metrics=true
mcp.server.timeout-seconds=60
mcp.server.features.experimental=false
mcp.server.features.analytics=true
mcp.server.features.debug-tools=falseUsage:
@Service
class ConfigurableService(
private val config: McpServerConfig
) {
fun doSomething() {
println("Max tools: ${config.maxTools}")
println("Experimental enabled: ${config.features.experimental}")
}
}Add validation constraints:
import javax.validation.constraints.Min
import javax.validation.constraints.Max
import javax.validation.constraints.NotBlank
import org.springframework.validation.annotation.Validated
@Configuration
@ConfigurationProperties(prefix = "mcp.server")
@Validated
data class ValidatedConfig(
@field:Min(1)
@field:Max(1000)
var maxTools: Int = 100,
@field:Min(1)
@field:Max(500)
var maxResources: Int = 50,
@field:Min(1)
var timeoutSeconds: Long = 30,
@field:NotBlank
var serverName: String = "mcp-server"
)Validation Errors:
***************************
APPLICATION FAILED TO START
***************************
Description:
Binding to target org.springframework.boot.context.properties.bind.BindException:
Property: mcp.server.maxTools
Value: 2000
Reason: must be less than or equal to 1000Activate beans based on property values:
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
// Activated when property matches value
@Service
@ConditionalOnProperty(
value = ["spring.ai.mcp.server.type"],
havingValue = "SYNC",
matchIfMissing = true // Active if property not set
)
class SyncOnlyService { /* ... */ }
// Activated when property exists (any value)
@Service
@ConditionalOnProperty(name = ["features.enabled"])
class FeatureService { /* ... */ }
// Multiple conditions (AND logic)
@Service
@ConditionalOnProperty(
value = ["spring.ai.mcp.server.type"],
havingValue = "ASYNC"
)
@ConditionalOnProperty(
name = ["features.advanced"],
havingValue = "true"
)
class AdvancedAsyncService { /* ... */ }Activate beans based on active profile:
import org.springframework.context.annotation.Profile
// Development profile only
@Service
@Profile("development")
class DevService { /* ... */ }
// Production profile only
@Service
@Profile("production")
class ProdService { /* ... */ }
// Multiple profiles (OR logic)
@Service
@Profile("development", "staging")
class DevOrStagingService { /* ... */ }
// Not production (negation)
@Service
@Profile("!production")
class NonProdService { /* ... */ }Activate when specific beans exist:
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
// Activated if ToolRegistry bean exists
@Service
@ConditionalOnBean(ToolRegistry::class)
class ToolDependentService { /* ... */ }
// Activated if no custom implementation exists
@Service
@ConditionalOnMissingBean(CustomRegistry::class)
class DefaultRegistryService { /* ... */ }Activate when classes are present on classpath:
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass
// Activated if WebFlux is available
@Configuration
@ConditionalOnClass(name = ["org.springframework.web.reactive.config.WebFluxConfigurer"])
class WebFluxConfiguration { /* ... */ }application.properties # Common configuration
application-dev.properties # Development overrides
application-staging.properties # Staging overrides
application-prod.properties # Production overridesapplication-dev.properties:
spring.ai.mcp.server.type=SYNC
spring.application.name=myapp-dev
features.experimental=true
features.debug-tools=true
logging.level.com.embabel.agent.mcpserver=DEBUGapplication-prod.properties:
spring.ai.mcp.server.type=ASYNC
spring.application.name=myapp-prod
features.experimental=false
features.debug-tools=false
logging.level.com.embabel.agent.mcpserver=INFOActivation:
# Command line
java -jar app.jar --spring.profiles.active=prod
# Environment variable
export SPRING_PROFILES_ACTIVE=prod
java -jar app.jarOverride properties with environment variables:
# Property: spring.ai.mcp.server.type
# Env var: SPRING_AI_MCP_SERVER_TYPE
export SPRING_AI_MCP_SERVER_TYPE=ASYNC
# Property: mcp.server.max-tools
# Env var: MCP_SERVER_MAX_TOOLS
export MCP_SERVER_MAX_TOOLS=500
java -jar app.jarAccess in Code:
@Service
class EnvAwareService(
@Value("\${API_KEY:default-key}")
private val apiKey: String,
@Value("\${MAX_CONNECTIONS:10}")
private val maxConnections: Int
) {
// Use apiKey and maxConnections
}@Configuration
@ConfigurationProperties(prefix = "mcp.auth")
data class AuthConfig(
var enabled: Boolean = false,
var provider: String = "basic",
var tokenExpiry: Long = 3600
)
@Configuration
@ConfigurationProperties(prefix = "mcp.storage")
data class StorageConfig(
var type: String = "memory",
var maxSize: Long = 1000,
var persistEnabled: Boolean = false
)
@Service
class AuthService(
private val authConfig: AuthConfig
) {
fun authenticate(token: String): Boolean {
if (!authConfig.enabled) return true
// Use authConfig.provider, authConfig.tokenExpiry
return false
}
}Properties:
mcp.auth.enabled=true
mcp.auth.provider=oauth2
mcp.auth.token-expiry=7200
mcp.storage.type=redis
mcp.storage.max-size=10000
mcp.storage.persist-enabled=trueimport org.springframework.cloud.context.config.annotation.RefreshScope
@Service
@RefreshScope // Enables runtime refresh
class DynamicConfigService(
private val config: McpServerConfig
) {
fun getCurrentMaxTools(): Int {
return config.maxTools // Reflects latest value after refresh
}
}Refresh Configuration:
# Trigger refresh (requires Spring Cloud)
curl -X POST http://localhost:8080/actuator/refreshimport org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Primary
@TestConfiguration
class TestMcpConfiguration {
@Bean
@Primary
fun testServerConfig(): McpServerConfig {
return McpServerConfig(
maxTools = 10,
maxResources = 5,
enableMetrics = false,
timeoutSeconds = 5
)
}
@Bean
fun testToolRegistry(): ToolRegistry {
return InMemoryToolRegistry()
}
}# src/test/resources/application-test.properties
spring.ai.mcp.server.type=SYNC
spring.application.name=test-app
features.experimental=false
mcp.server.timeout-seconds=5
mcp.storage.type=memoryimport org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.TestPropertySource
@SpringBootTest
@TestPropertySource(properties = [
"spring.ai.mcp.server.type=SYNC",
"features.experimental=true",
"mcp.server.max-tools=50"
])
class IntegrationTest {
@Autowired
private lateinit var serverStrategy: McpServerStrategy
@Autowired
private lateinit var config: McpServerConfig
@Test
fun `should configure server correctly`() {
assertEquals(McpExecutionMode.SYNC, serverStrategy.getExecutionMode())
assertEquals(50, config.maxTools)
}
}Use Type-Safe Configuration: Prefer @ConfigurationProperties over @Value
// Good
@ConfigurationProperties(prefix = "mcp.server")
data class McpConfig(var maxTools: Int = 100)
// Avoid scattered @Value
@Value("\${mcp.server.max-tools:100}")Provide Defaults: Always specify sensible default values
data class Config(
var maxTools: Int = 100, // Reasonable default
var timeoutSeconds: Long = 30 // Safe default
)Validate Configuration: Use validation annotations
@field:Min(1)
@field:Max(1000)
var maxTools: Int = 100Document Properties: Add KDoc or comments
/**
* Maximum number of tools (1-1000).
* Default: 100
*/
var maxTools: Int = 100Use Profiles Wisely: Keep environment-specific settings in profile files
# Common in application.properties
spring.application.name=myapp
# Environment-specific in application-{profile}.properties
spring.ai.mcp.server.type=ASYNC # in application-prod.properties