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

custom-implementations.mddocs/advanced/

Custom Implementations

Comprehensive guide to creating custom configurations, strategies, factories, and extending base classes in embabel-agent-mcpserver.

Table of Contents

Overview

The library is designed for extensibility at every layer. This guide shows how to extend and customize components to meet specific requirements.

Extension Points

┌─────────────────────────────────────────────┐
│         Extension Points                     │
├─────────────────────────────────────────────┤
│                                              │
│  Publishers                                  │
│  ├─ McpExportToolCallbackPublisher          │
│  ├─ McpResourcePublisher                    │
│  └─ McpPromptPublisher                      │
│                                              │
│  Registries                                  │
│  └─ ToolRegistry                            │
│                                              │
│  Strategies                                  │
│  └─ McpServerStrategy                       │
│                                              │
│  Factories                                   │
│  ├─ Resource Factories                      │
│  └─ Prompt Factories                        │
│                                              │
│  Configuration                               │
│  ├─ AbstractMcpServerConfiguration          │
│  └─ Custom Config Classes                   │
│                                              │
│  Utilities                                   │
│  ├─ StringTransformer                       │
│  ├─ HealthIndicator                         │
│  └─ Error Handlers                          │
└─────────────────────────────────────────────┘

Custom Publishers

Advanced Tool Publisher

Database-Backed Publisher: { .api }

@Service
class DatabaseToolPublisher(
    private val toolRepository: ToolDefinitionRepository,
    private val toolFactory: DynamicToolFactory
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback> by lazy {
        logger.info("Loading tools from database...")

        toolRepository.findAllActive()
            .mapNotNull { definition ->
                try {
                    toolFactory.createTool(definition)
                } catch (e: Exception) {
                    logger.error("Failed to create tool: ${definition.name}", e)
                    null
                }
            }
    }

    override fun infoString(verbose: Boolean?, indent: Int): String {
        return "DatabaseToolPublisher: ${toolCallbacks.size} tools"
    }

    companion object {
        private val logger = LoggerFactory.getLogger(DatabaseToolPublisher::class.java)
    }
}

@Repository
interface ToolDefinitionRepository : JpaRepository<ToolDefinition, Long> {
    fun findAllActive(): List<ToolDefinition>
}

@Entity
data class ToolDefinition(
    @Id @GeneratedValue
    val id: Long? = null,

    val name: String,
    val description: String,
    val implementation: String,  // Script or class name
    val inputSchema: String,     // JSON schema
    val active: Boolean = true
)

@Component
class DynamicToolFactory {
    fun createTool(definition: ToolDefinition): ToolCallback {
        return object : ToolCallback {
            override fun getName() = definition.name
            override fun getDescription() = definition.description
            override fun getInputTypeSchema() = definition.inputSchema

            override fun call(functionArguments: String): String {
                // Execute dynamic implementation
                return executeScript(definition.implementation, functionArguments)
            }
        }
    }

    private fun executeScript(script: String, args: String): String {
        // Script execution logic (Groovy, JavaScript, etc.)
        return "result"
    }
}

Plugin-Based Publisher

Load Tools from Plugins: { .api }

@Service
class PluginToolPublisher(
    private val pluginLoader: PluginLoader
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback> by lazy {
        logger.info("Loading tools from plugins...")

        val plugins = pluginLoader.loadPlugins()
        plugins.flatMap { plugin ->
            try {
                plugin.getTools()
            } catch (e: Exception) {
                logger.error("Failed to load tools from plugin: ${plugin.name}", e)
                emptyList()
            }
        }
    }

    override fun infoString(verbose: Boolean?, indent: Int): String {
        return "PluginToolPublisher: ${toolCallbacks.size} tools"
    }

    companion object {
        private val logger = LoggerFactory.getLogger(PluginToolPublisher::class.java)
    }
}

interface Plugin {
    val name: String
    val version: String
    fun getTools(): List<ToolCallback>
    fun initialize()
    fun shutdown()
}

@Component
class PluginLoader(
    @Value("\${plugins.directory:./plugins}") private val pluginsDir: String
) {

    private val classLoader = URLClassLoader.newInstance(
        File(pluginsDir).listFiles { file -> file.extension == "jar" }
            ?.map { it.toURI().toURL() }
            ?.toTypedArray() ?: emptyArray()
    )

    fun loadPlugins(): List<Plugin> {
        return ServiceLoader.load(Plugin::class.java, classLoader).toList()
    }
}

Composite Publisher

Combine Multiple Publishers: { .api }

@Service
class CompositePublisher(
    private val toolPublishers: List<McpExportToolCallbackPublisher>,
    private val resourcePublishers: List<McpResourcePublisher>,
    private val promptPublishers: List<McpPromptPublisher>
) : McpExportToolCallbackPublisher, McpResourcePublisher, McpPromptPublisher {

    // Aggregate tools from all tool publishers
    override val toolCallbacks: List<ToolCallback> by lazy {
        toolPublishers.flatMap { publisher ->
            try {
                publisher.toolCallbacks
            } catch (e: Exception) {
                logger.error("Failed to get tools from ${publisher::class.simpleName}", e)
                emptyList()
            }
        }
    }

    // Aggregate resources from all resource publishers
    override fun resources(): List<McpServerFeatures.SyncResourceSpecification> {
        return resourcePublishers.flatMap { publisher ->
            try {
                publisher.resources()
            } catch (e: Exception) {
                logger.error("Failed to get resources from ${publisher::class.simpleName}", e)
                emptyList()
            }
        }
    }

    // Aggregate prompts from all prompt publishers
    override fun prompts(): List<McpServerFeatures.SyncPromptSpecification> {
        return promptPublishers.flatMap { publisher ->
            try {
                publisher.prompts()
            } catch (e: Exception) {
                logger.error("Failed to get prompts from ${publisher::class.simpleName}", e)
                emptyList()
            }
        }
    }

    override fun infoString(verbose: Boolean?, indent: Int): String {
        return buildString {
            appendLine("CompositePublisher:")
            appendLine("  Tools: ${toolCallbacks.size} from ${toolPublishers.size} publishers")
            appendLine("  Resources: ${resources().size} from ${resourcePublishers.size} publishers")
            appendLine("  Prompts: ${prompts().size} from ${promptPublishers.size} publishers")
        }
    }

    companion object {
        private val logger = LoggerFactory.getLogger(CompositePublisher::class.java)
    }
}

Custom Tool Registry

Redis-Backed Registry

Distributed Tool Storage: { .api }

@Service
@Primary  // Replace default registry
class RedisToolRegistry(
    private val redisTemplate: RedisTemplate<String, ToolCallback>,
    private val objectMapper: ObjectMapper
) : ToolRegistry {

    private val keyPrefix = "mcp:tool:"

    override fun register(toolCallback: ToolCallback): Mono<Void> {
        return Mono.fromRunnable {
            val key = keyPrefix + toolCallback.name
            redisTemplate.opsForValue().set(key, toolCallback)
            logger.info("Registered tool in Redis: ${toolCallback.name}")
        }.subscribeOn(Schedulers.boundedElastic())
    }

    override fun unregister(toolName: String): Mono<Void> {
        return Mono.fromRunnable {
            val key = keyPrefix + toolName
            redisTemplate.delete(key)
            logger.info("Unregistered tool from Redis: $toolName")
        }.subscribeOn(Schedulers.boundedElastic())
    }

    override fun findToolCallback(toolName: String): Mono<ToolCallback> {
        return Mono.fromCallable {
            val key = keyPrefix + toolName
            redisTemplate.opsForValue().get(key)
                ?: throw ToolNotFoundException("Tool not found: $toolName")
        }.subscribeOn(Schedulers.boundedElastic())
    }

    override fun listToolCallbacks(): Mono<List<ToolCallback>> {
        return Mono.fromCallable {
            val pattern = "$keyPrefix*"
            val keys = redisTemplate.keys(pattern)

            keys.mapNotNull { key ->
                redisTemplate.opsForValue().get(key)
            }
        }.subscribeOn(Schedulers.boundedElastic())
    }

    override fun containsTool(toolName: String): Mono<Boolean> {
        return Mono.fromCallable {
            val key = keyPrefix + toolName
            redisTemplate.hasKey(key)
        }.subscribeOn(Schedulers.boundedElastic())
    }

    companion object {
        private val logger = LoggerFactory.getLogger(RedisToolRegistry::class.java)
    }
}

// Redis configuration
@Configuration
class RedisConfig {

    @Bean
    fun redisTemplate(
        connectionFactory: RedisConnectionFactory,
        objectMapper: ObjectMapper
    ): RedisTemplate<String, ToolCallback> {
        val template = RedisTemplate<String, ToolCallback>()
        template.connectionFactory = connectionFactory

        // Custom serialization for ToolCallback
        template.keySerializer = StringRedisSerializer()
        template.valueSerializer = Jackson2JsonRedisSerializer(objectMapper, ToolCallback::class.java)

        return template
    }
}

Hierarchical Registry

Multi-Level Tool Namespaces: { .api }

@Service
class HierarchicalToolRegistry : ToolRegistry {

    private val rootRegistry = ConcurrentHashMap<String, ToolCallback>()
    private val namespaceRegistries = ConcurrentHashMap<String, ConcurrentHashMap<String, ToolCallback>>()

    // Register tool in namespace
    fun registerInNamespace(namespace: String, toolCallback: ToolCallback): Mono<Void> {
        return Mono.fromRunnable {
            val registry = namespaceRegistries.computeIfAbsent(namespace) {
                ConcurrentHashMap()
            }
            registry[toolCallback.name] = toolCallback
            logger.info("Registered ${toolCallback.name} in namespace: $namespace")
        }
    }

    // Find tool in specific namespace
    fun findInNamespace(namespace: String, toolName: String): Mono<ToolCallback> {
        return Mono.fromCallable {
            namespaceRegistries[namespace]?.get(toolName)
                ?: throw ToolNotFoundException("Tool not found in namespace $namespace: $toolName")
        }
    }

    // List tools in namespace
    fun listInNamespace(namespace: String): Mono<List<ToolCallback>> {
        return Mono.fromCallable {
            namespaceRegistries[namespace]?.values?.toList() ?: emptyList()
        }
    }

    // Default ToolRegistry implementation (searches all namespaces)
    override fun register(toolCallback: ToolCallback): Mono<Void> {
        return Mono.fromRunnable {
            rootRegistry[toolCallback.name] = toolCallback
        }
    }

    override fun findToolCallback(toolName: String): Mono<ToolCallback> {
        return Mono.fromCallable {
            // Search root registry
            rootRegistry[toolName]
                ?: // Search all namespaces
                namespaceRegistries.values
                    .mapNotNull { it[toolName] }
                    .firstOrNull()
                ?: throw ToolNotFoundException(toolName)
        }
    }

    override fun listToolCallbacks(): Mono<List<ToolCallback>> {
        return Mono.fromCallable {
            val allTools = rootRegistry.values.toMutableList()
            namespaceRegistries.values.forEach { registry ->
                allTools.addAll(registry.values)
            }
            allTools
        }
    }

    override fun unregister(toolName: String): Mono<Void> {
        return Mono.fromRunnable {
            rootRegistry.remove(toolName)
            namespaceRegistries.values.forEach { it.remove(toolName) }
        }
    }

    override fun containsTool(toolName: String): Mono<Boolean> {
        return findToolCallback(toolName)
            .map { true }
            .onErrorReturn(false)
    }

    companion object {
        private val logger = LoggerFactory.getLogger(HierarchicalToolRegistry::class.java)
    }
}

// Usage
@Service
class NamespacedPublisher(
    private val registry: HierarchicalToolRegistry
) {

    @PostConstruct
    fun registerTools() {
        registry.registerInNamespace("api", createApiTool()).subscribe()
        registry.registerInNamespace("admin", createAdminTool()).subscribe()
        registry.registerInNamespace("internal", createInternalTool()).subscribe()
    }

    private fun createApiTool(): ToolCallback = TODO()
    private fun createAdminTool(): ToolCallback = TODO()
    private fun createInternalTool(): ToolCallback = TODO()
}

Custom Server Strategy

Hybrid Server Strategy

Combine Sync and Async Behaviors: { .api }

@Service
class HybridServerStrategy(
    private val syncServer: McpSyncServer,
    private val asyncServer: McpAsyncServer,
    private val toolRegistry: ToolRegistry,
    private val serverInfo: ServerInfo,
    @Value("\${mcp.hybrid.mode:ADAPTIVE}") private val hybridMode: HybridMode
) : McpServerStrategy {

    enum class HybridMode {
        ADAPTIVE,     // Choose based on tool characteristics
        PREFER_SYNC,  // Use sync unless tool requires async
        PREFER_ASYNC  // Use async unless tool requires sync
    }

    override fun addToolCallback(toolCallback: ToolCallback): Mono<Void> {
        return when (selectMode(toolCallback)) {
            McpExecutionMode.SYNC -> {
                Mono.fromRunnable {
                    syncServer.registerTool(toolCallback)
                    toolRegistry.register(toolCallback).block()
                }
            }
            McpExecutionMode.ASYNC -> {
                asyncServer.registerToolAsync(toolCallback)
                    .then(toolRegistry.register(toolCallback))
            }
        }
    }

    override fun removeToolCallback(toolName: String): Mono<Void> {
        return toolRegistry.findToolCallback(toolName)
            .flatMap { tool ->
                when (selectMode(tool)) {
                    McpExecutionMode.SYNC -> Mono.fromRunnable {
                        syncServer.unregisterTool(toolName)
                    }
                    McpExecutionMode.ASYNC -> asyncServer.unregisterToolAsync(toolName)
                }
            }
            .then(toolRegistry.unregister(toolName))
    }

    override fun getServerInfo(): Mono<ServerInfo> {
        return Mono.just(serverInfo.copy(
            executionMode = McpExecutionMode.SYNC,  // Report as sync (primary)
            capabilities = serverInfo.capabilities + McpCapability.HYBRID
        ))
    }

    override fun getExecutionMode(): McpExecutionMode {
        return McpExecutionMode.SYNC  // Report primary mode
    }

    override fun checkHealth(): Mono<ServerHealthStatus> {
        return Mono.zip(
            checkSyncHealth(),
            checkAsyncHealth()
        ).map { (syncHealth, asyncHealth) ->
            ServerHealthStatus(
                isHealthy = syncHealth.isHealthy && asyncHealth.isHealthy,
                message = "Hybrid mode: sync=${syncHealth.isHealthy}, async=${asyncHealth.isHealthy}",
                details = mapOf(
                    "sync" to syncHealth,
                    "async" to asyncHealth
                )
            )
        }
    }

    private fun selectMode(tool: ToolCallback): McpExecutionMode {
        return when (hybridMode) {
            HybridMode.ADAPTIVE -> {
                // Heuristics: check if tool is I/O-bound, reactive, etc.
                if (isIOBound(tool) || isReactive(tool)) {
                    McpExecutionMode.ASYNC
                } else {
                    McpExecutionMode.SYNC
                }
            }
            HybridMode.PREFER_SYNC -> {
                if (requiresAsync(tool)) McpExecutionMode.ASYNC else McpExecutionMode.SYNC
            }
            HybridMode.PREFER_ASYNC -> {
                if (requiresSync(tool)) McpExecutionMode.SYNC else McpExecutionMode.ASYNC
            }
        }
    }

    private fun isIOBound(tool: ToolCallback): Boolean {
        // Check tool metadata, annotations, or characteristics
        return tool.description.contains("database") ||
               tool.description.contains("http") ||
               tool.description.contains("file")
    }

    private fun isReactive(tool: ToolCallback): Boolean {
        // Check if tool returns reactive types
        return false  // Implement based on tool inspection
    }

    private fun requiresAsync(tool: ToolCallback): Boolean = false
    private fun requiresSync(tool: ToolCallback): Boolean = false

    private fun checkSyncHealth(): Mono<ServerHealthStatus> = TODO()
    private fun checkAsyncHealth(): Mono<ServerHealthStatus> = TODO()
}

enum class McpCapability {
    TOOLS, RESOURCES, PROMPTS, HYBRID
}

Custom Naming Strategies

Advanced Naming Strategy

Version and Environment Aware: { .api }

class VersionedEnvironmentNamingStrategy(
    private val version: String,
    private val environment: String,
    private val namespace: String
) : StringTransformer {

    override fun transform(input: String): String {
        return buildString {
            // Environment prefix (dev, staging, prod)
            append(environment.lowercase())
            append("_")

            // API version
            append(version.replace(".", "_"))
            append("_")

            // Namespace
            append(namespace.lowercase())
            append("_")

            // Original name
            append(sanitize(input))
        }
    }

    private fun sanitize(input: String): String {
        return input
            .replace(Regex("[^a-zA-Z0-9_]"), "_")
            .lowercase()
            .take(50)
    }
}

// Usage
@Service
class VersionedPublisher(
    @Value("\${api.version}") private val apiVersion: String,
    @Value("\${spring.profiles.active}") private val environment: String
) : McpExportToolCallbackPublisher {

    private val namingStrategy = VersionedEnvironmentNamingStrategy(
        version = apiVersion,
        environment = environment,
        namespace = "api"
    )

    override val toolCallbacks: List<ToolCallback>
        get() {
            val toolObject = ToolObject(
                objects = listOf(tool1, tool2),
                namingStrategy = namingStrategy
            )
            return McpToolExport.fromToolObject(toolObject).toolCallbacks
        }

    override fun infoString(verbose: Boolean?, indent: Int) = "VersionedPublisher"

    private val tool1: Any = TODO()
    private val tool2: Any = TODO()
}

// Results in names like: "prod_v2_0_api_toolname"

Context-Aware Naming

Tenant and User Context: { .api }

class TenantAwareNamingStrategy(
    private val tenantResolver: TenantResolver,
    private val baseStrategy: StringTransformer
) : StringTransformer {

    override fun transform(input: String): String {
        val tenant = tenantResolver.getCurrentTenant()
        val baseName = baseStrategy.transform(input)

        return "${tenant.id}_$baseName"
    }
}

interface TenantResolver {
    fun getCurrentTenant(): Tenant
}

data class Tenant(val id: String, val name: String)

@Component
class RequestContextTenantResolver : TenantResolver {
    override fun getCurrentTenant(): Tenant {
        // Extract from request context, security context, etc.
        return Tenant("tenant1", "Example Tenant")
    }
}

Custom Resource Factories

Template-Based Resource Factory

Resource Generation from Templates: { .api }

@Component
class TemplateResourceFactory(
    private val templateEngine: TemplateEngine,
    private val resourceLoader: ResourceLoader
) {

    fun createFromTemplate(
        uri: String,
        name: String,
        templatePath: String,
        dataProvider: () -> Map<String, Any>
    ): SyncResourceSpecification {

        return SyncResourceSpecificationFactory.syncResourceSpecification(
            uri = uri,
            name = name,
            description = "Generated from template: $templatePath",
            resourceLoader = { exchange ->
                val template = loadTemplate(templatePath)
                val data = dataProvider()
                renderTemplate(template, data)
            },
            mimeType = "text/html"
        )
    }

    private fun loadTemplate(path: String): String {
        return resourceLoader.getResource(path)
            .inputStream
            .bufferedReader()
            .use { it.readText() }
    }

    private fun renderTemplate(template: String, data: Map<String, Any>): String {
        return templateEngine.process(template, data)
    }
}

@Service
class TemplateResourcePublisher(
    private val factory: TemplateResourceFactory
) : McpResourcePublisher {

    override fun resources(): List<SyncResourceSpecification> {
        return listOf(
            factory.createFromTemplate(
                uri = "app://docs/api-reference",
                name = "APIReference",
                templatePath = "classpath:templates/api-reference.html",
                dataProvider = { getApiData() }
            ),
            factory.createFromTemplate(
                uri = "app://reports/dashboard",
                name = "Dashboard",
                templatePath = "classpath:templates/dashboard.html",
                dataProvider = { getDashboardData() }
            )
        )
    }

    override fun infoString(verbose: Boolean?, indent: Int) = "TemplateResourcePublisher"

    private fun getApiData(): Map<String, Any> = mapOf(
        "endpoints" to listOf("/api/v1/users", "/api/v1/data"),
        "version" to "1.0.0"
    )

    private fun getDashboardData(): Map<String, Any> = mapOf(
        "stats" to mapOf("users" to 100, "requests" to 1000)
    )
}

interface TemplateEngine {
    fun process(template: String, data: Map<String, Any>): String
}

Custom Prompt Factories

Intelligent Prompt Factory

Generate Prompts with AI Assistance: { .api }

@Component
class IntelligentPromptFactory(
    private val schemaGenerator: JsonSchemaGenerator,
    private val descriptionEnhancer: DescriptionEnhancer
) {

    fun createIntelligentPrompt(
        goalName: String,
        inputType: Class<*>,
        baseDescription: String
    ): SyncPromptSpecification {

        // Generate JSON schema with enhanced descriptions
        val schema = schemaGenerator.generateSchema(inputType)
        val enhancedSchema = descriptionEnhancer.enhanceSchema(schema)

        // Create goal with enhanced description
        val goal = object : Named, Described {
            override val name = goalName
            override val description = descriptionEnhancer.enhanceDescription(baseDescription)
        }

        return McpPromptFactory().syncPromptSpecificationForType(goal, inputType)
    }

    fun createConversationalPrompt(
        goalName: String,
        inputType: Class<*>,
        conversationStarter: String
    ): SyncPromptSpecification {

        val goal = object : Named, Described {
            override val name = goalName
            override val description = """
                $conversationStarter

                Please provide the following information:
                ${generateFieldPrompts(inputType)}
            """.trimIndent()
        }

        return McpPromptFactory().syncPromptSpecificationForType(goal, inputType)
    }

    private fun generateFieldPrompts(inputType: Class<*>): String {
        return inputType.declaredFields
            .joinToString("\n") { field ->
                val annotation = field.getAnnotation(JsonPropertyDescription::class.java)
                "- ${field.name}: ${annotation?.value ?: "Required field"}"
            }
    }
}

@Component
class DescriptionEnhancer {
    fun enhanceDescription(description: String): String {
        // Use AI or NLP to enhance descriptions
        return description + " (Enhanced with contextual information)"
    }

    fun enhanceSchema(schema: JsonSchema): JsonSchema {
        // Add examples, constraints, better descriptions
        return schema
    }
}

Custom Configuration Classes

Multi-Environment Configuration

Environment-Specific Settings: { .api }

@Configuration
@ConfigurationProperties(prefix = "mcp.custom")
data class CustomMcpConfiguration(
    var defaultTimeout: Long = 30000,
    var maxToolsPerPublisher: Int = 100,
    var enableMetrics: Boolean = true,
    var enableCaching: Boolean = true,
    var environments: Map<String, EnvironmentConfig> = emptyMap()
)

data class EnvironmentConfig(
    var timeout: Long? = null,
    var maxTools: Int? = null,
    var features: Set<String> = emptySet()
)

@Configuration
class EnvironmentAwareConfiguration(
    private val customConfig: CustomMcpConfiguration,
    private val environment: Environment
) {

    @Bean
    fun environmentSettings(): EnvironmentSettings {
        val activeProfile = environment.activeProfiles.firstOrNull() ?: "default"
        val envConfig = customConfig.environments[activeProfile]

        return EnvironmentSettings(
            timeout = envConfig?.timeout ?: customConfig.defaultTimeout,
            maxTools = envConfig?.maxTools ?: customConfig.maxToolsPerPublisher,
            features = envConfig?.features ?: emptySet()
        )
    }
}

data class EnvironmentSettings(
    val timeout: Long,
    val maxTools: Int,
    val features: Set<String>
)

// application.yml
/*
mcp:
  custom:
    default-timeout: 30000
    max-tools-per-publisher: 100
    enable-metrics: true
    enable-caching: true
    environments:
      development:
        timeout: 60000
        max-tools: 200
        features: [debug, profiling]
      production:
        timeout: 15000
        max-tools: 50
        features: [metrics, alerting]
*/

Custom Health Indicators

Comprehensive Health Check

Multi-Aspect Health Monitoring: { .api }

@Component
class ComprehensiveMcpHealthIndicator(
    private val serverStrategy: McpServerStrategy,
    private val toolRegistry: ToolRegistry,
    private val meterRegistry: MeterRegistry
) : HealthIndicator {

    override fun health(): Health {
        val checks = listOf(
            checkServerStatus(),
            checkToolRegistry(),
            checkMemoryUsage(),
            checkThreadPool(),
            checkErrorRate()
        )

        val allHealthy = checks.all { it.status == Status.UP }
        val builder = if (allHealthy) Health.up() else Health.down()

        checks.forEach { check ->
            builder.withDetail(check.name, check.details)
        }

        return builder.build()
    }

    private fun checkServerStatus(): HealthCheck {
        return try {
            val serverInfo = serverStrategy.getServerInfo()
                .block(Duration.ofSeconds(1))

            HealthCheck(
                name = "server",
                status = Status.UP,
                details = mapOf(
                    "name" to (serverInfo?.name ?: "unknown"),
                    "mode" to (serverInfo?.executionMode?.toString() ?: "unknown")
                )
            )
        } catch (e: Exception) {
            HealthCheck(
                name = "server",
                status = Status.DOWN,
                details = mapOf("error" to (e.message ?: "Unknown error"))
            )
        }
    }

    private fun checkToolRegistry(): HealthCheck {
        return try {
            val toolCount = toolRegistry.listToolCallbacks()
                .map { it.size }
                .block(Duration.ofSeconds(1))

            val status = if ((toolCount ?: 0) > 0) Status.UP else Status.DOWN

            HealthCheck(
                name = "tools",
                status = status,
                details = mapOf("count" to (toolCount ?: 0))
            )
        } catch (e: Exception) {
            HealthCheck(
                name = "tools",
                status = Status.DOWN,
                details = mapOf("error" to (e.message ?: "Unknown error"))
            )
        }
    }

    private fun checkMemoryUsage(): HealthCheck {
        val runtime = Runtime.getRuntime()
        val usedMemory = runtime.totalMemory() - runtime.freeMemory()
        val maxMemory = runtime.maxMemory()
        val usagePercent = (usedMemory.toDouble() / maxMemory * 100).toInt()

        val status = when {
            usagePercent > 90 -> Status.DOWN
            usagePercent > 80 -> Status.OUT_OF_SERVICE
            else -> Status.UP
        }

        return HealthCheck(
            name = "memory",
            status = status,
            details = mapOf(
                "usagePercent" to usagePercent,
                "used" to usedMemory,
                "max" to maxMemory
            )
        )
    }

    private fun checkThreadPool(): HealthCheck {
        // Check thread pool metrics
        return HealthCheck(
            name = "threads",
            status = Status.UP,
            details = mapOf("active" to Thread.activeCount())
        )
    }

    private fun checkErrorRate(): HealthCheck {
        val errorRate = try {
            meterRegistry.find("method.error").counters()
                .sumOf { it.count() }
        } catch (e: Exception) {
            0.0
        }

        val status = if (errorRate > 100) Status.OUT_OF_SERVICE else Status.UP

        return HealthCheck(
            name = "errors",
            status = status,
            details = mapOf("totalErrors" to errorRate)
        )
    }

    data class HealthCheck(
        val name: String,
        val status: Status,
        val details: Map<String, Any>
    )
}

Custom Error Handlers

Global Error Handler

Centralized Error Management: { .api }

@ControllerAdvice
class McpServerErrorHandler(
    private val meterRegistry: MeterRegistry
) {

    @ExceptionHandler(ToolNotFoundException::class)
    fun handleToolNotFound(e: ToolNotFoundException): ResponseEntity<ErrorResponse> {
        meterRegistry.counter("errors", "type", "tool_not_found").increment()

        return ResponseEntity
            .status(HttpStatus.NOT_FOUND)
            .body(ErrorResponse(
                error = "TOOL_NOT_FOUND",
                message = e.message ?: "Tool not found",
                timestamp = System.currentTimeMillis()
            ))
    }

    @ExceptionHandler(ToolRegistrationException::class)
    fun handleRegistrationError(e: ToolRegistrationException): ResponseEntity<ErrorResponse> {
        meterRegistry.counter("errors", "type", "registration_failed").increment()

        logger.error("Tool registration failed", e)

        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(ErrorResponse(
                error = "REGISTRATION_FAILED",
                message = e.message ?: "Failed to register tool",
                timestamp = System.currentTimeMillis()
            ))
    }

    @ExceptionHandler(Exception::class)
    fun handleGenericError(e: Exception): ResponseEntity<ErrorResponse> {
        meterRegistry.counter("errors", "type", "unknown").increment()

        logger.error("Unexpected error", e)

        return ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .body(ErrorResponse(
                error = "INTERNAL_ERROR",
                message = "An unexpected error occurred",
                timestamp = System.currentTimeMillis()
            ))
    }

    data class ErrorResponse(
        val error: String,
        val message: String,
        val timestamp: Long
    )

    companion object {
        private val logger = LoggerFactory.getLogger(McpServerErrorHandler::class.java)
    }
}

Extension Patterns

Decorator Pattern

Enhance Tool Behavior: { .api }

class LoggingToolDecorator(
    private val delegate: ToolCallback
) : ToolCallback by delegate {

    override fun call(functionArguments: String): String {
        val start = System.currentTimeMillis()
        logger.info("Executing tool: ${delegate.name}")

        return try {
            val result = delegate.call(functionArguments)
            val duration = System.currentTimeMillis() - start
            logger.info("Tool ${delegate.name} completed in ${duration}ms")
            result
        } catch (e: Exception) {
            logger.error("Tool ${delegate.name} failed", e)
            throw e
        }
    }

    companion object {
        private val logger = LoggerFactory.getLogger(LoggingToolDecorator::class.java)
    }
}

// Usage
@Service
class DecoratedPublisher : McpExportToolCallbackPublisher {
    override val toolCallbacks = listOf(
        LoggingToolDecorator(originalTool1),
        LoggingToolDecorator(originalTool2)
    )

    override fun infoString(verbose: Boolean?, indent: Int) = "DecoratedPublisher"

    private val originalTool1: ToolCallback = TODO()
    private val originalTool2: ToolCallback = TODO()
}

Chain of Responsibility

Processing Pipeline: { .api }

interface ToolProcessor {
    fun process(tool: ToolCallback, args: String): ProcessResult
}

data class ProcessResult(
    val success: Boolean,
    val result: String?,
    val continueProcessing: Boolean = true
)

class ValidationProcessor : ToolProcessor {
    override fun process(tool: ToolCallback, args: String): ProcessResult {
        return if (args.isNotBlank()) {
            ProcessResult(true, null, true)
        } else {
            ProcessResult(false, """{"error":"Empty arguments"}""", false)
        }
    }
}

class CachingProcessor(private val cache: Cache) : ToolProcessor {
    override fun process(tool: ToolCallback, args: String): ProcessResult {
        val cached = cache.get("${tool.name}:$args")
        return if (cached != null) {
            ProcessResult(true, cached, false)  // Don't continue, use cache
        } else {
            ProcessResult(true, null, true)  // Continue processing
        }
    }
}

class ExecutionProcessor : ToolProcessor {
    override fun process(tool: ToolCallback, args: String): ProcessResult {
        return try {
            val result = tool.call(args)
            ProcessResult(true, result, false)
        } catch (e: Exception) {
            ProcessResult(false, """{"error":"${e.message}"}""", false)
        }
    }
}

class ProcessorChain(
    private val processors: List<ToolProcessor>
) : ToolCallback {
    lateinit var delegate: ToolCallback

    override fun getName() = delegate.name
    override fun getDescription() = delegate.description

    override fun call(functionArguments: String): String {
        for (processor in processors) {
            val result = processor.process(delegate, functionArguments)

            if (!result.continueProcessing) {
                return result.result ?: """{"error":"No result"}"""
            }

            if (!result.success) {
                return result.result ?: """{"error":"Processing failed"}"""
            }
        }

        return """{"error":"No processor handled the request"}"""
    }
}

Related Documentation

  • Architecture - Architecture overview
  • Embabel Integration - Framework integration
  • Performance - Performance optimization
  • Error Handling - Error strategies
  • Publishers API - Publisher interfaces
  • Server Strategy API - Strategy interface
tessl i tessl/maven-com-embabel-agent--embabel-agent-mcpserver@0.3.1

docs

advanced

architecture.md

custom-implementations.md

embabel-integration.md

error-handling.md

imports.md

performance.md

index.md

tile.json