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