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

server-strategy.mddocs/api/

Server Strategy API

API reference for McpServerStrategy interface and its implementations for managing MCP server operations.

Package

com.embabel.agent.mcpserver        // Interface
com.embabel.agent.mcpserver.sync   // Sync implementation
com.embabel.agent.mcpserver.async  // Async implementation

Overview

McpServerStrategy provides a unified interface for server operations across both synchronous and asynchronous execution modes. All operations return Mono<T> for reactive composition.

Interface

McpServerStrategy { .api }

Core interface for MCP server operations.

package com.embabel.agent.mcpserver

import org.springframework.ai.tool.ToolCallback
import reactor.core.publisher.Mono

interface McpServerStrategy {
    // Tool management
    fun addToolCallback(toolCallback: ToolCallback): Mono<Void>
    fun removeToolCallback(toolName: String): Mono<Void>

    // Server information
    fun getServerInfo(): Mono<ServerInfo>
    fun getExecutionMode(): McpExecutionMode

    // Health check
    fun checkHealth(): Mono<ServerHealthStatus>
}

Methods:

addToolCallback { .api }

Register a new tool at runtime.

fun addToolCallback(toolCallback: ToolCallback): Mono<Void>

Parameters:

  • toolCallback: ToolCallback - Tool callback to register

Returns: Mono<Void> - Completes when tool is registered

Usage:

import com.embabel.agent.mcpserver.McpServerStrategy
import org.springframework.ai.tool.ToolCallback
import org.springframework.stereotype.Service

@Service
class ToolManager(
    private val serverStrategy: McpServerStrategy
) {

    fun registerTool(toolCallback: ToolCallback) {
        serverStrategy.addToolCallback(toolCallback)
            .subscribe(
                { println("Tool registered successfully") },
                { error -> println("Failed to register tool: ${error.message}") }
            )
    }

    fun registerMultipleTools(tools: List<ToolCallback>) {
        Flux.fromIterable(tools)
            .flatMap { tool -> serverStrategy.addToolCallback(tool) }
            .then()
            .subscribe(
                { println("All tools registered") },
                { error -> println("Registration failed: ${error.message}") }
            )
    }
}

Error Handling:

serverStrategy.addToolCallback(tool)
    .doOnSubscribe { logger.info("Registering tool: ${tool.name}") }
    .doOnSuccess { logger.info("Successfully registered: ${tool.name}") }
    .doOnError { error -> logger.error("Failed to register: ${tool.name}", error) }
    .retry(3)  // Retry up to 3 times
    .onErrorResume { error ->
        logger.error("All retries failed", error)
        Mono.empty()  // Continue without this tool
    }
    .subscribe()

removeToolCallback { .api }

Unregister a tool by name.

fun removeToolCallback(toolName: String): Mono<Void>

Parameters:

  • toolName: String - Name of tool to remove

Returns: Mono<Void> - Completes when tool is removed

Usage:

@Service
class ToolRemovalService(
    private val serverStrategy: McpServerStrategy
) {

    fun removeTool(toolName: String) {
        serverStrategy.removeToolCallback(toolName)
            .subscribe(
                { logger.info("Tool removed: $toolName") },
                { error -> logger.error("Failed to remove tool", error) }
            )
    }

    fun removeMultipleTools(toolNames: List<String>) {
        Flux.fromIterable(toolNames)
            .flatMap { name -> serverStrategy.removeToolCallback(name) }
            .then()
            .subscribe(
                { logger.info("All tools removed") },
                { error -> logger.error("Removal failed", error) }
            )
    }

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

Safe Removal:

@Service
class SafeRemovalService(
    private val serverStrategy: McpServerStrategy,
    private val toolRegistry: ToolRegistry
) {

    fun safeRemove(toolName: String): Mono<Boolean> {
        return toolRegistry.findToolCallback(toolName)
            .flatMap { existingTool ->
                serverStrategy.removeToolCallback(toolName)
                    .thenReturn(true)
            }
            .defaultIfEmpty(false)
            .doOnSuccess { removed ->
                if (removed) {
                    logger.info("Removed: $toolName")
                } else {
                    logger.warn("Tool not found: $toolName")
                }
            }
    }

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

getServerInfo { .api }

Retrieve server metadata and capabilities.

fun getServerInfo(): Mono<ServerInfo>

Returns: Mono<ServerInfo> - Server information

Usage:

@Service
class ServerInfoService(
    private val serverStrategy: McpServerStrategy
) {

    fun displayServerInfo() {
        serverStrategy.getServerInfo()
            .subscribe { info ->
                println("Server: ${info.name}")
                println("Version: ${info.version}")
                println("Mode: ${info.executionMode}")
                println("Capabilities: ${info.capabilities}")
            }
    }

    fun checkCapability(capability: McpCapability): Mono<Boolean> {
        return serverStrategy.getServerInfo()
            .map { info -> info.capabilities.contains(capability) }
    }
}

getExecutionMode { .api }

Get current execution mode (sync or async).

fun getExecutionMode(): McpExecutionMode

Returns: McpExecutionMode - Current execution mode

Usage:

@Service
class ModeAwareService(
    private val serverStrategy: McpServerStrategy
) {

    fun performModeSpecificOperation() {
        val mode = serverStrategy.getExecutionMode()

        when (mode) {
            McpExecutionMode.SYNC -> {
                println("Running in synchronous mode")
                performSyncOperation()
            }
            McpExecutionMode.ASYNC -> {
                println("Running in asynchronous mode")
                performAsyncOperation()
            }
        }
    }

    private fun performSyncOperation() { /* sync-specific logic */ }
    private fun performAsyncOperation() { /* async-specific logic */ }
}

checkHealth { .api }

Check server health status.

fun checkHealth(): Mono<ServerHealthStatus>

Returns: Mono<ServerHealthStatus> - Health status information

Usage:

@Service
class HealthCheckService(
    private val serverStrategy: McpServerStrategy
) {

    fun performHealthCheck(): Mono<HealthReport> {
        return serverStrategy.checkHealth()
            .map { status ->
                HealthReport(
                    healthy = status.isHealthy,
                    message = status.message,
                    details = status.details
                )
            }
    }

    fun waitUntilHealthy(timeout: Duration): Mono<Boolean> {
        return serverStrategy.checkHealth()
            .map { it.isHealthy }
            .repeatWhenEmpty { it.delayElements(Duration.ofSeconds(1)) }
            .timeout(timeout)
            .onErrorReturn(false)
    }
}

data class HealthReport(
    val healthy: Boolean,
    val message: String,
    val details: Map<String, Any>
)

Implementations

SyncServerStrategy { .api }

Synchronous implementation of McpServerStrategy.

package com.embabel.agent.mcpserver.sync

class SyncServerStrategy(
    private val mcpSyncServer: McpSyncServer,
    private val toolRegistry: ToolRegistry,
    private val serverInfo: ServerInfo
) : McpServerStrategy {
    // Implementation delegates to McpSyncServer
}

Characteristics:

  • Wraps blocking operations in Mono.fromRunnable() or Mono.fromCallable()
  • Compatible with traditional servlet-based applications
  • Lower memory overhead per operation
  • Simpler error handling and debugging

Auto-Configuration:

# Activates SyncServerStrategy
spring.ai.mcp.server.type=SYNC

Bean Access:

import org.springframework.stereotype.Service

@Service
class MyService(
    private val serverStrategy: McpServerStrategy  // Injected as SyncServerStrategy
) {
    // Use serverStrategy for operations
}

AsyncServerStrategy { .api }

Asynchronous implementation of McpServerStrategy.

package com.embabel.agent.mcpserver.async

class AsyncServerStrategy(
    private val mcpAsyncServer: McpAsyncServer,
    private val toolRegistry: ToolRegistry,
    private val serverInfo: ServerInfo
) : McpServerStrategy {
    // Implementation uses native Mono operations
}

Characteristics:

  • Native reactive operations without blocking
  • Scalable for high concurrency
  • Better resource utilization
  • Integrates with reactive libraries

Auto-Configuration:

# Activates AsyncServerStrategy
spring.ai.mcp.server.type=ASYNC

Bean Access:

import org.springframework.stereotype.Service

@Service
class MyService(
    private val serverStrategy: McpServerStrategy  // Injected as AsyncServerStrategy
) {
    // Use serverStrategy for operations
}

Usage Patterns

Lifecycle Management { .api }

@Service
class ToolLifecycleManager(
    private val serverStrategy: McpServerStrategy
) {

    @PostConstruct
    fun registerStartupTools() {
        val startupTools = listOf(
            createHealthCheckTool(),
            createVersionTool()
        )

        Flux.fromIterable(startupTools)
            .flatMap { serverStrategy.addToolCallback(it) }
            .then()
            .subscribe(
                { logger.info("Startup tools registered") },
                { error -> logger.error("Startup registration failed", error) }
            )
    }

    @PreDestroy
    fun cleanupTools() {
        // Remove temporary tools on shutdown
        toolRegistry.listToolCallbacks()
            .flatMapMany { Flux.fromIterable(it) }
            .filter { callback -> callback.name.startsWith("temp_") }
            .flatMap { callback ->
                serverStrategy.removeToolCallback(callback.name)
            }
            .then()
            .block()  // Block on shutdown
    }

    private fun createHealthCheckTool(): ToolCallback = TODO()
    private fun createVersionTool(): ToolCallback = TODO()

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

Hot Reload { .api }

@Service
class HotReloadService(
    private val serverStrategy: McpServerStrategy,
    private val toolRegistry: ToolRegistry
) {

    fun reloadTool(toolName: String, newImplementation: ToolCallback): Mono<Void> {
        return toolRegistry.findToolCallback(toolName)
            .flatMap { existingTool ->
                serverStrategy.removeToolCallback(toolName)
            }
            .then(serverStrategy.addToolCallback(newImplementation))
            .doOnSuccess { logger.info("Reloaded: $toolName") }
            .doOnError { error -> logger.error("Reload failed", error) }
    }

    fun reloadAllTools(implementations: Map<String, ToolCallback>): Mono<Void> {
        return Flux.fromIterable(implementations.entries)
            .flatMap { (name, impl) -> reloadTool(name, impl) }
            .then()
    }

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

Feature Flags { .api }

@Service
class FeatureFlagService(
    private val serverStrategy: McpServerStrategy,
    private val toolRegistry: ToolRegistry
) {

    fun toggleFeature(featureName: String, enabled: Boolean): Mono<Void> {
        return if (enabled) {
            enableFeature(featureName)
        } else {
            disableFeature(featureName)
        }
    }

    private fun enableFeature(featureName: String): Mono<Void> {
        val tool = createFeatureTool(featureName)
        return serverStrategy.addToolCallback(tool)
            .doOnSuccess { logger.info("Feature enabled: $featureName") }
    }

    private fun disableFeature(featureName: String): Mono<Void> {
        return toolRegistry.listToolCallbacks()
            .flatMapMany { Flux.fromIterable(it) }
            .filter { callback -> callback.name.startsWith("feature_$featureName") }
            .flatMap { callback -> serverStrategy.removeToolCallback(callback.name) }
            .then()
            .doOnSuccess { logger.info("Feature disabled: $featureName") }
    }

    private fun createFeatureTool(featureName: String): ToolCallback = TODO()

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

REST API Management { .api }

@RestController
@RequestMapping("/admin/tools")
class ToolManagementController(
    private val serverStrategy: McpServerStrategy,
    private val toolRegistry: ToolRegistry
) {

    @PostMapping("/register")
    fun registerTool(@RequestBody request: RegisterToolRequest): Mono<ResponseEntity<String>> {
        val tool = createToolFromRequest(request)
        return serverStrategy.addToolCallback(tool)
            .thenReturn(ResponseEntity.ok("Tool registered: ${request.name}"))
            .onErrorReturn(ResponseEntity.status(500).body("Registration failed"))
    }

    @DeleteMapping("/{toolName}")
    fun removeTool(@PathVariable toolName: String): Mono<ResponseEntity<String>> {
        return serverStrategy.removeToolCallback(toolName)
            .thenReturn(ResponseEntity.ok("Tool removed: $toolName"))
            .onErrorReturn(ResponseEntity.status(500).body("Removal failed"))
    }

    @GetMapping("/info")
    fun getServerInfo(): Mono<ResponseEntity<ServerInfo>> {
        return serverStrategy.getServerInfo()
            .map { ResponseEntity.ok(it) }
    }

    @GetMapping("/health")
    fun checkHealth(): Mono<ResponseEntity<ServerHealthStatus>> {
        return serverStrategy.checkHealth()
            .map { ResponseEntity.ok(it) }
    }

    private fun createToolFromRequest(request: RegisterToolRequest): ToolCallback = TODO()
}

data class RegisterToolRequest(
    val name: String,
    val description: String,
    val implementation: String
)

Best Practices

  1. Use Reactive Operators: Chain operations for efficiency

    serverStrategy.addToolCallback(tool1)
        .then(serverStrategy.addToolCallback(tool2))
        .then(serverStrategy.addToolCallback(tool3))
        .subscribe()
  2. Handle Errors: Always provide error handlers

    serverStrategy.addToolCallback(tool)
        .subscribe(
            { logger.info("Success") },
            { error -> logger.error("Failed", error) }
        )
  3. Use Logging: Log all operations for debugging

    serverStrategy.addToolCallback(tool)
        .doOnSubscribe { logger.info("Starting registration") }
        .doOnSuccess { logger.info("Registration successful") }
        .doOnError { error -> logger.error("Registration failed", error) }
        .subscribe()
  4. Validate Before Operations: Check preconditions

    fun registerIfValid(tool: ToolCallback): Mono<Void> {
        return validateTool(tool)
            .filter { it }
            .flatMap { serverStrategy.addToolCallback(tool) }
            .switchIfEmpty(Mono.error(IllegalArgumentException("Invalid tool")))
    }
  5. Use Timeouts: Set reasonable timeouts

    serverStrategy.addToolCallback(tool)
        .timeout(Duration.ofSeconds(30))
        .onErrorResume(TimeoutException::class.java) { error ->
            logger.error("Registration timeout", error)
            Mono.empty()
        }
        .subscribe()

See Also

  • Dynamic Tool Management Guide - Runtime tool operations
  • Tool Registry API - Tool query operations
  • Domain Types API - ServerInfo, ServerHealthStatus
  • Execution Modes Guide - Mode selection
tessl i tessl/maven-com-embabel-agent--embabel-agent-mcpserver@0.3.1

docs

index.md

tile.json