Discover and Export available Agent(s) as MCP Servers
API reference for McpServerStrategy interface and its implementations for managing MCP server operations.
com.embabel.agent.mcpserver // Interface
com.embabel.agent.mcpserver.sync // Sync implementation
com.embabel.agent.mcpserver.async // Async implementationMcpServerStrategy provides a unified interface for server operations across both synchronous and asynchronous execution modes. All operations return Mono<T> for reactive composition.
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:
Register a new tool at runtime.
fun addToolCallback(toolCallback: ToolCallback): Mono<Void>Parameters:
toolCallback: ToolCallback - Tool callback to registerReturns: 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()Unregister a tool by name.
fun removeToolCallback(toolName: String): Mono<Void>Parameters:
toolName: String - Name of tool to removeReturns: 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)
}
}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) }
}
}Get current execution mode (sync or async).
fun getExecutionMode(): McpExecutionModeReturns: 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 */ }
}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>
)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:
Mono.fromRunnable() or Mono.fromCallable()Auto-Configuration:
# Activates SyncServerStrategy
spring.ai.mcp.server.type=SYNCBean Access:
import org.springframework.stereotype.Service
@Service
class MyService(
private val serverStrategy: McpServerStrategy // Injected as SyncServerStrategy
) {
// Use serverStrategy for operations
}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:
Auto-Configuration:
# Activates AsyncServerStrategy
spring.ai.mcp.server.type=ASYNCBean Access:
import org.springframework.stereotype.Service
@Service
class MyService(
private val serverStrategy: McpServerStrategy // Injected as AsyncServerStrategy
) {
// Use serverStrategy for operations
}@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)
}
}@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)
}
}@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)
}
}@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
)Use Reactive Operators: Chain operations for efficiency
serverStrategy.addToolCallback(tool1)
.then(serverStrategy.addToolCallback(tool2))
.then(serverStrategy.addToolCallback(tool3))
.subscribe()Handle Errors: Always provide error handlers
serverStrategy.addToolCallback(tool)
.subscribe(
{ logger.info("Success") },
{ error -> logger.error("Failed", error) }
)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()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")))
}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()