Discover and Export available Agent(s) as MCP Servers
Complete guide to configuring embabel-agent-mcpserver including properties, conditional beans, profiles, and advanced configuration patterns.
# Execution mode (SYNC or ASYNC)
spring.ai.mcp.server.type=SYNC
# Application name (used by publishers)
spring.application.name=my-agent-apispring.ai.mcp.server.typeControls execution mode and auto-configuration.
Values:
SYNC (default): Synchronous blocking operationsASYNC: Asynchronous non-blocking operationsImpact:
@Configuration classes# Development: Use sync for simplicity
spring.ai.mcp.server.type=SYNC
# Production: Use async for scalability
spring.ai.mcp.server.type=ASYNCspring.application.nameApplication identifier used in logging and server metadata.
# Default
spring.application.name=agent-api
# Custom
spring.application.name=payment-service# Development profile
spring.ai.mcp.server.type=SYNC
spring.application.name=myapp-dev
# Enable debug logging
logging.level.com.embabel.agent.mcpserver=DEBUG
# Development features
features.experimental=true
features.debug-tools=true# Production profile
spring.ai.mcp.server.type=ASYNC
spring.application.name=myapp-prod
# Production logging
logging.level.com.embabel.agent.mcpserver=INFO
# Production features
features.experimental=false
features.debug-tools=false# Command line
java -jar app.jar --spring.profiles.active=prod
# Environment variable
export SPRING_PROFILES_ACTIVE=prod
java -jar app.jar
# application.properties
spring.profiles.active=devimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Service
@Service
@ConditionalOnProperty(
value = ["spring.ai.mcp.server.type"],
havingValue = "SYNC",
matchIfMissing = true // Active when property not set
)
class SyncOnlyPublisher : McpResourcePublisher {
override fun resources() = listOf(/* sync resources */)
override fun infoString(verbose: Boolean?, indent: Int) = "SyncOnlyPublisher"
}@Service
@ConditionalOnProperty(
value = ["spring.ai.mcp.server.type"],
havingValue = "ASYNC"
)
class AsyncOnlyPublisher : McpAsyncResourcePublisher {
override fun resources() = listOf(/* async resources */)
override fun infoString(verbose: Boolean?, indent: Int) = "AsyncOnlyPublisher"
}import org.springframework.context.annotation.Profile
@Service
@Profile("development")
class DevelopmentToolsPublisher : McpExportToolCallbackPublisher {
override val toolCallbacks = listOf(
createDebugTool(),
createProfilingTool(),
createTestDataTool()
)
override fun infoString(verbose: Boolean?, indent: Int) =
"DevelopmentToolsPublisher: ${toolCallbacks.size} tools"
private fun createDebugTool(): ToolCallback = TODO()
private fun createProfilingTool(): ToolCallback = TODO()
private fun createTestDataTool(): ToolCallback = TODO()
}
@Service
@Profile("production")
class ProductionToolsPublisher : McpExportToolCallbackPublisher {
override val toolCallbacks = listOf(
createHealthCheckTool(),
createMetricsTool()
)
override fun infoString(verbose: Boolean?, indent: Int) =
"ProductionToolsPublisher: ${toolCallbacks.size} tools"
private fun createHealthCheckTool(): ToolCallback = TODO()
private fun createMetricsTool(): ToolCallback = TODO()
}@Service
@ConditionalOnProperty(
name = ["features.experimental"],
havingValue = "true"
)
class ExperimentalFeaturesPublisher : McpExportToolCallbackPublisher {
override val toolCallbacks = listOf(/* experimental tools */)
override fun infoString(verbose: Boolean?, indent: Int) =
"ExperimentalFeaturesPublisher"
}
@Service
@ConditionalOnProperty(
name = ["features.analytics"],
havingValue = "true"
)
class AnalyticsPublisher : McpResourcePublisher {
override fun resources() = listOf(/* analytics resources */)
override fun infoString(verbose: Boolean?, indent: Int) = "AnalyticsPublisher"
}import org.springframework.boot.autoconfigure.condition.ConditionalOnBean
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
@Service
@ConditionalOnProperty(
value = ["spring.ai.mcp.server.type"],
havingValue = "ASYNC"
)
@ConditionalOnProperty(
name = ["features.advanced"],
havingValue = "true"
)
class AdvancedAsyncPublisher : McpAsyncResourcePublisher {
override fun resources() = listOf(/* advanced async resources */)
override fun infoString(verbose: Boolean?, indent: Int) =
"AdvancedAsyncPublisher"
}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
)# application.properties
mcp.server.max-tools=200
mcp.server.max-resources=100
mcp.server.enable-metrics=true
mcp.server.timeout-seconds=60@Service
class ConfigurablePublisher(
private val config: McpServerConfig
) : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() {
val tools = createAllTools()
return tools.take(config.maxTools) // Respect configuration limit
}
override fun infoString(verbose: Boolean?, indent: Int): String {
return "ConfigurablePublisher: ${toolCallbacks.size}/${config.maxTools} tools"
}
private fun createAllTools(): List<ToolCallback> = listOf(/* all tools */)
}Organize configuration by domain:
// Authentication configuration
@Configuration
@ConfigurationProperties(prefix = "mcp.auth")
data class AuthConfig(
var enabled: Boolean = false,
var provider: String = "basic",
var tokenExpiry: Long = 3600
)
// Storage configuration
@Configuration
@ConfigurationProperties(prefix = "mcp.storage")
data class StorageConfig(
var type: String = "memory",
var maxSize: Long = 1000,
var persistEnabled: Boolean = false
)
// Use in publishers
@Service
class AuthToolsPublisher(
private val authConfig: AuthConfig
) : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() = if (authConfig.enabled) {
listOf(createLoginTool(), createLogoutTool())
} else {
emptyList()
}
override fun infoString(verbose: Boolean?, indent: Int) =
"AuthToolsPublisher: ${toolCallbacks.size} tools"
private fun createLoginTool(): ToolCallback = TODO()
private fun createLogoutTool(): ToolCallback = TODO()
}Respond to configuration changes at runtime:
import org.springframework.cloud.context.config.annotation.RefreshScope
@Service
@RefreshScope // Enables dynamic configuration refresh
class DynamicConfigPublisher(
private val config: McpServerConfig,
private val serverStrategy: McpServerStrategy
) : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() = createToolsBasedOnConfig()
override fun infoString(verbose: Boolean?, indent: Int) =
"DynamicConfigPublisher: ${toolCallbacks.size} tools"
private fun createToolsBasedOnConfig(): List<ToolCallback> {
return if (config.enableMetrics) {
listOf(createMetricsTool())
} else {
emptyList()
}
}
private fun createMetricsTool(): ToolCallback = TODO()
}@Service
class EnvironmentAwarePublisher(
@Value("\${API_KEY:default-key}")
private val apiKey: String,
@Value("\${MAX_CONNECTIONS:10}")
private val maxConnections: Int,
@Value("\${FEATURE_ENABLED:false}")
private val featureEnabled: Boolean
) : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() {
logger.info("API Key: ${apiKey.take(4)}***")
logger.info("Max Connections: $maxConnections")
logger.info("Feature Enabled: $featureEnabled")
return if (featureEnabled) {
createFeatureTools()
} else {
emptyList()
}
}
override fun infoString(verbose: Boolean?, indent: Int) =
"EnvironmentAwarePublisher: ${toolCallbacks.size} tools"
private fun createFeatureTools(): List<ToolCallback> = listOf(/* tools */)
companion object {
private val logger = LoggerFactory.getLogger(EnvironmentAwarePublisher::class.java)
}
}# Root logging level
logging.level.root=INFO
# Package-specific logging
logging.level.com.embabel.agent.mcpserver=DEBUG
logging.level.io.modelcontextprotocol=DEBUG
# Spring logging
logging.level.org.springframework.boot=INFO# Detailed MCP server logging
logging.level.com.embabel.agent.mcpserver.sync=TRACE
logging.level.com.embabel.agent.mcpserver.async=TRACE
logging.level.com.embabel.agent.mcpserver.domain=DEBUG
# Publisher logging
logging.level.com.myapp.publishers=DEBUG
# Tool execution logging
logging.level.org.springframework.ai.tool=DEBUG# Console pattern
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n
# File logging
logging.file.name=logs/mcp-server.log
logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
# Log rotation
logging.logback.rollingpolicy.max-file-size=10MB
logging.logback.rollingpolicy.max-history=30@TestConfiguration
class TestMcpConfiguration {
@Bean
@Primary
fun testMcpServerConfig(): McpServerConfig {
return McpServerConfig(
maxTools = 10,
maxResources = 5,
enableMetrics = false,
timeoutSeconds = 5
)
}
}# src/test/resources/application-test.properties
spring.ai.mcp.server.type=SYNC
spring.application.name=test-app
# Disable features in tests
features.experimental=false
features.analytics=false
# Fast timeouts for tests
mcp.server.timeout-seconds=5
# Memory storage for tests
mcp.storage.type=memory@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
@Test
fun `should configure server correctly`() {
// Test with configured properties
}
}// Good: Type-safe configuration class
@Configuration
@ConfigurationProperties(prefix = "mcp")
data class McpConfig(
var maxTools: Int = 100,
var timeout: Duration = Duration.ofSeconds(30)
)
// Avoid: Scattered @Value annotations
@Value("\${mcp.max-tools:100}")
private val maxTools: Int = 100@Configuration
@ConfigurationProperties(prefix = "mcp.server")
data class ServerConfig(
var maxTools: Int = 100, // Reasonable default
var maxResources: Int = 50, // Reasonable default
var timeoutSeconds: Long = 30, // Reasonable default
var enableMetrics: Boolean = true // Safe default
)import javax.validation.constraints.Min
import javax.validation.constraints.Max
@Configuration
@ConfigurationProperties(prefix = "mcp.server")
@Validated
data class ValidatedConfig(
@field:Min(1)
@field:Max(1000)
var maxTools: Int = 100,
@field:Min(1)
var timeoutSeconds: Long = 30
)@Configuration
@ConfigurationProperties(prefix = "mcp.server")
data class DocumentedConfig(
/**
* Maximum number of tools that can be registered.
* Default: 100
* Range: 1-1000
*/
var maxTools: Int = 100,
/**
* Request timeout in seconds.
* Default: 30
* Minimum: 1
*/
var timeoutSeconds: Long = 30
)# Common configuration in application.properties
spring.application.name=myapp
mcp.server.max-tools=100
# Environment-specific in application-{profile}.properties
# application-dev.properties
spring.ai.mcp.server.type=SYNC
features.debug=true
# application-prod.properties
spring.ai.mcp.server.type=ASYNC
features.debug=falseSymptom: Configuration properties not taking effect
Solutions:
@ConfigurationProperties has correct prefix@Configuration or @Component annotation present// Check configuration is loaded
@Configuration
@ConfigurationProperties(prefix = "mcp.server")
data class Config(var maxTools: Int = 100) {
@PostConstruct
fun logConfig() {
println("Max Tools: $maxTools")
}
}Symptom: Bean with @ConditionalOnProperty not created
Solutions:
matchIfMissing setting@ConditionalOnExpression for complex conditions// Add logging to verify condition
@Service
@ConditionalOnProperty(
value = ["spring.ai.mcp.server.type"],
havingValue = "ASYNC"
)
class AsyncPublisher : McpAsyncResourcePublisher {
init {
println("AsyncPublisher created!")
}
// ...
}Symptom: Profile-specific beans not loading
Solutions:
spring.profiles.activespring.profiles.active=dev,debug# Check active profiles at startup
java -jar app.jar --debug
# Look for: "The following profiles are active: dev"# Minimal setup
spring.ai.mcp.server.type=SYNC# Standard production setup
spring.ai.mcp.server.type=ASYNC
spring.application.name=myapp
logging.level.com.embabel.agent.mcpserver=INFO
mcp.server.max-tools=200
mcp.server.enable-metrics=true# Complete configuration example
spring.ai.mcp.server.type=ASYNC
spring.application.name=payment-service
spring.profiles.active=production
# Logging
logging.level.root=INFO
logging.level.com.embabel.agent.mcpserver=DEBUG
logging.file.name=logs/mcp-server.log
# MCP Server
mcp.server.max-tools=500
mcp.server.max-resources=200
mcp.server.enable-metrics=true
mcp.server.timeout-seconds=60
# Features
features.experimental=false
features.analytics=true
features.debug-tools=false
# Storage
mcp.storage.type=redis
mcp.storage.max-size=10000
# Authentication
mcp.auth.enabled=true
mcp.auth.provider=oauth2