Discover and Export available Agent(s) as MCP Servers
—
Guide to runtime tool management using McpServerStrategy for adding, removing, and querying tools dynamically.
The McpServerStrategy interface provides runtime control over MCP server tools:
Access server operations through dependency injection:
import com.embabel.agent.mcpserver.McpServerStrategy
import org.springframework.stereotype.Service
@Service
class ToolManager(
private val serverStrategy: McpServerStrategy
) {
fun addTool(toolCallback: ToolCallback) {
serverStrategy.addToolCallback(toolCallback)
.subscribe(
{ println("Tool added successfully") },
{ error -> println("Failed to add tool: ${error.message}") }
)
}
fun removeTool(toolName: String) {
serverStrategy.removeToolCallback(toolName)
.subscribe(
{ println("Tool removed successfully") },
{ error -> println("Failed to remove tool: ${error.message}") }
)
}
}Key Points:
Mono<Void> for reactive compositionimport org.springframework.ai.tool.ToolCallback
import reactor.core.publisher.Mono
@Service
class DynamicToolService(
private val serverStrategy: McpServerStrategy
) {
fun registerCalculatorTools() {
val addTool = createAddTool()
val subtractTool = createSubtractTool()
serverStrategy.addToolCallback(addTool)
.then(serverStrategy.addToolCallback(subtractTool))
.subscribe(
{ logger.info("Calculator tools registered") },
{ error -> logger.error("Registration failed", error) }
)
}
private fun createAddTool(): ToolCallback {
return object : ToolCallback {
override fun getName(): String = "add"
override fun getDescription(): String = "Add two numbers"
override fun call(functionArguments: String): String {
// Parse arguments and perform addition
return "Result"
}
}
}
private fun createSubtractTool(): ToolCallback {
// Similar implementation
TODO()
}
companion object {
private val logger = LoggerFactory.getLogger(DynamicToolService::class.java)
}
}Register tools based on configuration or runtime conditions:
@Service
class ConditionalToolService(
private val serverStrategy: McpServerStrategy,
@Value("\${features.experimental:false}")
private val experimentalEnabled: Boolean
) {
@PostConstruct
fun registerTools() {
// Always register core tools
registerCoreTools()
// Conditionally register experimental tools
if (experimentalEnabled) {
registerExperimentalTools()
}
}
private fun registerCoreTools() {
val coreTool = createCoreTool()
serverStrategy.addToolCallback(coreTool).subscribe()
}
private fun registerExperimentalTools() {
val experimentalTool = createExperimentalTool()
serverStrategy.addToolCallback(experimentalTool)
.subscribe(
{ logger.info("Experimental tools enabled") },
{ error -> logger.warn("Failed to register experimental tools", error) }
)
}
private fun createCoreTool(): ToolCallback = TODO()
private fun createExperimentalTool(): ToolCallback = TODO()
companion object {
private val logger = LoggerFactory.getLogger(ConditionalToolService::class.java)
}
}Register multiple tools efficiently:
@Service
class BatchToolService(
private val serverStrategy: McpServerStrategy
) {
fun registerToolBatch(tools: List<ToolCallback>) {
Flux.fromIterable(tools)
.flatMap { tool ->
serverStrategy.addToolCallback(tool)
.doOnSuccess { logger.info("Registered: ${tool.name}") }
.onErrorResume { error ->
logger.error("Failed to register ${tool.name}", error)
Mono.empty() // Continue with other tools
}
}
.then()
.subscribe(
{ logger.info("Batch registration completed") },
{ error -> logger.error("Batch registration failed", error) }
)
}
companion object {
private val logger = LoggerFactory.getLogger(BatchToolService::class.java)
}
}@Service
class ToolRemovalService(
private val serverStrategy: McpServerStrategy
) {
fun unregisterTool(toolName: String) {
serverStrategy.removeToolCallback(toolName)
.subscribe(
{ logger.info("Tool removed: $toolName") },
{ error -> logger.error("Failed to remove tool", error) }
)
}
fun unregisterMultipleTools(toolNames: List<String>) {
Flux.fromIterable(toolNames)
.flatMap { name ->
serverStrategy.removeToolCallback(name)
.doOnSuccess { logger.info("Removed: $name") }
}
.then()
.subscribe()
}
companion object {
private val logger = LoggerFactory.getLogger(ToolRemovalService::class.java)
}
}Check if tool exists before removing:
@Service
class SafeToolRemovalService(
private val serverStrategy: McpServerStrategy,
private val toolRegistry: ToolRegistry
) {
fun safeRemoveTool(toolName: String): Mono<Boolean> {
return toolRegistry.findToolCallback(toolName)
.flatMap { existingTool ->
serverStrategy.removeToolCallback(toolName)
.thenReturn(true)
}
.defaultIfEmpty(false)
.doOnSuccess { removed ->
if (removed) {
logger.info("Successfully removed: $toolName")
} else {
logger.warn("Tool not found: $toolName")
}
}
}
companion object {
private val logger = LoggerFactory.getLogger(SafeToolRemovalService::class.java)
}
}Query registered tools through the registry:
import com.embabel.agent.mcpserver.ToolRegistry
@Service
class ToolQueryService(
private val toolRegistry: ToolRegistry
) {
fun getAllToolNames(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks -> callbacks.map { it.name } }
}
fun findTool(toolName: String): Mono<ToolCallback> {
return toolRegistry.findToolCallback(toolName)
}
fun toolExists(toolName: String): Mono<Boolean> {
return toolRegistry.findToolCallback(toolName)
.map { true }
.defaultIfEmpty(false)
}
fun getToolCount(): Mono<Int> {
return toolRegistry.listToolCallbacks()
.map { it.size }
}
}Inspect tool properties:
@Service
class ToolInspectionService(
private val toolRegistry: ToolRegistry
) {
fun inspectTool(toolName: String): Mono<ToolInfo> {
return toolRegistry.findToolCallback(toolName)
.map { callback ->
ToolInfo(
name = callback.name,
description = callback.description,
schemaType = callback.inputTypeSchema?.typeName ?: "unknown"
)
}
}
fun listAllTools(): Mono<List<ToolInfo>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.map { callback ->
ToolInfo(
name = callback.name,
description = callback.description,
schemaType = callback.inputTypeSchema?.typeName ?: "unknown"
)
}
}
}
}
data class ToolInfo(
val name: String,
val description: String,
val schemaType: String
)Register tools during application initialization:
@Service
class StartupToolRegistration(
private val serverStrategy: McpServerStrategy
) {
@PostConstruct
fun registerStartupTools() {
val startupTools = listOf(
createHealthCheckTool(),
createVersionTool(),
createHelpTool()
)
Flux.fromIterable(startupTools)
.flatMap { serverStrategy.addToolCallback(it) }
.then()
.subscribe(
{ logger.info("Startup tools registered") },
{ error -> logger.error("Startup registration failed", error) }
)
}
private fun createHealthCheckTool(): ToolCallback = TODO()
private fun createVersionTool(): ToolCallback = TODO()
private fun createHelpTool(): ToolCallback = TODO()
companion object {
private val logger = LoggerFactory.getLogger(StartupToolRegistration::class.java)
}
}Register tools when needed:
@RestController
@RequestMapping("/admin/tools")
class ToolManagementController(
private val serverStrategy: McpServerStrategy
) {
@PostMapping("/enable/{feature}")
fun enableFeature(@PathVariable feature: String): Mono<ResponseEntity<String>> {
val tool = createFeatureTool(feature)
return serverStrategy.addToolCallback(tool)
.thenReturn(ResponseEntity.ok("Feature enabled: $feature"))
.onErrorReturn(ResponseEntity.status(500).body("Failed to enable feature"))
}
@DeleteMapping("/disable/{toolName}")
fun disableTool(@PathVariable toolName: String): Mono<ResponseEntity<String>> {
return serverStrategy.removeToolCallback(toolName)
.thenReturn(ResponseEntity.ok("Tool disabled: $toolName"))
.onErrorReturn(ResponseEntity.status(500).body("Failed to disable tool"))
}
private fun createFeatureTool(feature: String): ToolCallback = TODO()
}Remove tools during shutdown:
@Service
class ShutdownToolCleanup(
private val serverStrategy: McpServerStrategy,
private val toolRegistry: ToolRegistry
) {
@PreDestroy
fun cleanupTools() {
toolRegistry.listToolCallbacks()
.flatMapMany { Flux.fromIterable(it) }
.filter { callback -> callback.name.startsWith("temp_") }
.flatMap { callback ->
serverStrategy.removeToolCallback(callback.name)
.doOnSuccess { logger.info("Cleaned up: ${callback.name}") }
}
.then()
.block() // Block on shutdown
}
companion object {
private val logger = LoggerFactory.getLogger(ShutdownToolCleanup::class.java)
}
}Reload tool definitions without restart:
@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 ->
// Remove old implementation
serverStrategy.removeToolCallback(toolName)
}
.then(
// Add new implementation
serverStrategy.addToolCallback(newImplementation)
)
.doOnSuccess { logger.info("Reloaded: $toolName") }
.doOnError { error -> logger.error("Reload failed for $toolName", error) }
}
}Dynamic tool registration based on feature flags:
@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)
}
}Manage multiple versions of tools:
@Service
class ToolVersioningService(
private val serverStrategy: McpServerStrategy
) {
fun registerVersionedTool(
baseName: String,
version: String,
implementation: ToolCallback
): Mono<Void> {
val versionedName = "${baseName}_$version"
val versionedTool = object : ToolCallback by implementation {
override fun getName(): String = versionedName
}
return serverStrategy.addToolCallback(versionedTool)
.doOnSuccess { logger.info("Registered $baseName version $version") }
}
fun deprecateVersion(baseName: String, version: String): Mono<Void> {
val toolName = "${baseName}_$version"
return serverStrategy.removeToolCallback(toolName)
.doOnSuccess { logger.info("Deprecated $baseName version $version") }
}
companion object {
private val logger = LoggerFactory.getLogger(ToolVersioningService::class.java)
}
}@Service
class ResilientToolService(
private val serverStrategy: McpServerStrategy
) {
fun registerWithRetry(
toolCallback: ToolCallback,
maxRetries: Int = 3
): Mono<Void> {
return serverStrategy.addToolCallback(toolCallback)
.retry(maxRetries.toLong())
.doOnError { error ->
logger.error("Failed to register ${toolCallback.name} after $maxRetries attempts", error)
}
}
fun registerWithFallback(
primaryTool: ToolCallback,
fallbackTool: ToolCallback
): Mono<Void> {
return serverStrategy.addToolCallback(primaryTool)
.onErrorResume { error ->
logger.warn("Primary tool registration failed, using fallback", error)
serverStrategy.addToolCallback(fallbackTool)
}
}
companion object {
private val logger = LoggerFactory.getLogger(ResilientToolService::class.java)
}
}@Service
class ValidatingToolService(
private val serverStrategy: McpServerStrategy,
private val toolRegistry: ToolRegistry
) {
fun registerWithValidation(toolCallback: ToolCallback): Mono<Void> {
return validateTool(toolCallback)
.flatMap { isValid ->
if (isValid) {
serverStrategy.addToolCallback(toolCallback)
} else {
Mono.error(IllegalArgumentException("Tool validation failed"))
}
}
}
private fun validateTool(toolCallback: ToolCallback): Mono<Boolean> {
return Mono.fromCallable {
// Check name is not empty
if (toolCallback.name.isBlank()) return@fromCallable false
// Check description is provided
if (toolCallback.description.isBlank()) return@fromCallable false
// Check name doesn't conflict
true
}
}
}// Good: Chain operations
serverStrategy.addToolCallback(tool1)
.then(serverStrategy.addToolCallback(tool2))
.then(serverStrategy.addToolCallback(tool3))
.subscribe()
// Less efficient: Sequential subscribes
serverStrategy.addToolCallback(tool1).subscribe()
serverStrategy.addToolCallback(tool2).subscribe()
serverStrategy.addToolCallback(tool3).subscribe()// Good: Handle errors
serverStrategy.addToolCallback(tool)
.subscribe(
{ logger.info("Success") },
{ error -> logger.error("Failed", error) }
)
// Bad: Ignore errors
serverStrategy.addToolCallback(tool).subscribe()// Good: Clear naming
val toolName = "payment_process_refund"
// Bad: Generic naming
val toolName = "tool1"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) }
.subscribe()import reactor.test.StepVerifier
class ToolManagementServiceTest {
private lateinit var serverStrategy: McpServerStrategy
private lateinit var service: DynamicToolService
@BeforeEach
fun setup() {
serverStrategy = mock()
service = DynamicToolService(serverStrategy)
}
@Test
fun `should add tool successfully`() {
val tool = mock<ToolCallback>()
whenever(serverStrategy.addToolCallback(any())).thenReturn(Mono.empty())
StepVerifier.create(serverStrategy.addToolCallback(tool))
.verifyComplete()
}
@Test
fun `should handle registration failure`() {
val tool = mock<ToolCallback>()
val error = RuntimeException("Registration failed")
whenever(serverStrategy.addToolCallback(any())).thenReturn(Mono.error(error))
StepVerifier.create(serverStrategy.addToolCallback(tool))
.expectError(RuntimeException::class.java)
.verify()
}
}