CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-com-embabel-agent--embabel-agent-mcpserver

Discover and Export available Agent(s) as MCP Servers

Overview
Eval results
Files

configuration.mddocs/guides/

Configuration Guide

Complete guide to configuring embabel-agent-mcpserver including properties, conditional beans, profiles, and advanced configuration patterns.

Configuration Properties

Core Properties

# Execution mode (SYNC or ASYNC)
spring.ai.mcp.server.type=SYNC

# Application name (used by publishers)
spring.application.name=my-agent-api

Property Details

spring.ai.mcp.server.type

Controls execution mode and auto-configuration.

Values:

  • SYNC (default): Synchronous blocking operations
  • ASYNC: Asynchronous non-blocking operations

Impact:

  • Activates mode-specific @Configuration classes
  • Determines which publisher interfaces are available
  • Affects server implementation (McpSyncServer vs McpAsyncServer)
# Development: Use sync for simplicity
spring.ai.mcp.server.type=SYNC

# Production: Use async for scalability
spring.ai.mcp.server.type=ASYNC

spring.application.name

Application identifier used in logging and server metadata.

# Default
spring.application.name=agent-api

# Custom
spring.application.name=payment-service

Environment-Specific Configuration

Profile-Based Configuration

application-dev.properties

# 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

application-prod.properties

# 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

Activate Profiles

# 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=dev

Conditional Beans

Mode-Based Conditionals

Sync Mode Only

import 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"
}

Async Mode Only

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

Profile-Based Conditionals

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

Feature Flag Conditionals

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

Multiple Conditions

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

Custom Configuration Classes

Configuration Bean

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

Use Configuration

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

Advanced Configuration Patterns

Multi-Module Configuration

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

Dynamic Configuration

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

Environment Variable Configuration

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

Logging Configuration

Basic Logging

# 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 Logging

# 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

Log Pattern Configuration

# 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

Testing Configuration

Test Configuration

@TestConfiguration
class TestMcpConfiguration {

    @Bean
    @Primary
    fun testMcpServerConfig(): McpServerConfig {
        return McpServerConfig(
            maxTools = 10,
            maxResources = 5,
            enableMetrics = false,
            timeoutSeconds = 5
        )
    }
}

Test Properties

# 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

Integration Test Configuration

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

Configuration Best Practices

1. Use Type-Safe Configuration

// 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

2. Provide Sensible Defaults

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

3. Validate Configuration

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
)

4. Document Properties

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

5. Use Profiles Appropriately

# 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=false

Troubleshooting

Configuration Not Applied

Symptom: Configuration properties not taking effect

Solutions:

  1. Check property names match exactly (including hyphens vs camel case)
  2. Verify @ConfigurationProperties has correct prefix
  3. Ensure @Configuration or @Component annotation present
  4. Check for typos in property names
// Check configuration is loaded
@Configuration
@ConfigurationProperties(prefix = "mcp.server")
data class Config(var maxTools: Int = 100) {
    @PostConstruct
    fun logConfig() {
        println("Max Tools: $maxTools")
    }
}

Conditional Bean Not Activated

Symptom: Bean with @ConditionalOnProperty not created

Solutions:

  1. Verify property value matches exactly (case-sensitive)
  2. Check matchIfMissing setting
  3. Use @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!")
    }
    // ...
}

Profile Not Active

Symptom: Profile-specific beans not loading

Solutions:

  1. Check active profiles: spring.profiles.active
  2. Verify profile name matches file name
  3. Use multiple profiles if needed: spring.profiles.active=dev,debug
# Check active profiles at startup
java -jar app.jar --debug

# Look for: "The following profiles are active: dev"

Example Configurations

Minimal Configuration

# Minimal setup
spring.ai.mcp.server.type=SYNC

Standard Configuration

# 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

# 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

Related Documentation

  • Execution Modes - Mode selection and implications
  • Creating Publishers - Conditional publisher patterns
  • Server Configuration API - Configuration API reference
  • Getting Started - Basic setup walkthrough
tessl i tessl/maven-com-embabel-agent--embabel-agent-mcpserver@0.3.1

docs

index.md

tile.json