Discover and Export available Agent(s) as MCP Servers
API reference for ToolRegistry interface and implementations for querying and managing registered tools.
com.embabel.agent.mcpserver // Interface
com.embabel.agent.mcpserver.sync // Sync implementation
com.embabel.agent.mcpserver.async // Async implementationToolRegistry provides a centralized registry for querying registered tools. It enables runtime inspection of available tools and their specifications.
Core interface for tool registry operations.
package com.embabel.agent.mcpserver
import org.springframework.ai.tool.ToolCallback
import reactor.core.publisher.Mono
interface ToolRegistry {
fun listToolCallbacks(): Mono<List<ToolCallback>>
fun findToolCallback(toolName: String): Mono<ToolCallback>
fun hasToolCallback(toolName: String): Mono<Boolean>
}Methods:
Retrieve all registered tool callbacks.
fun listToolCallbacks(): Mono<List<ToolCallback>>Returns: Mono<List<ToolCallback>> - List of all registered tools
Usage:
import com.embabel.agent.mcpserver.ToolRegistry
import org.springframework.stereotype.Service
@Service
class ToolQueryService(
private val toolRegistry: ToolRegistry
) {
fun getAllToolNames(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks -> callbacks.map { it.name } }
}
fun countTools(): Mono<Int> {
return toolRegistry.listToolCallbacks()
.map { it.size }
}
fun printAllTools() {
toolRegistry.listToolCallbacks()
.subscribe { callbacks ->
println("Registered tools:")
callbacks.forEach { callback ->
println("- ${callback.name}: ${callback.description}")
}
}
}
}Filter Tools:
@Service
class FilteringService(
private val toolRegistry: ToolRegistry
) {
fun findToolsByPrefix(prefix: String): Mono<List<ToolCallback>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.filter { it.name.startsWith(prefix) }
}
}
fun findToolsByPattern(pattern: Regex): Mono<List<ToolCallback>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.filter { it.name.matches(pattern) }
}
}
fun groupToolsByPrefix(): Mono<Map<String, List<ToolCallback>>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.groupBy { callback ->
callback.name.substringBefore('_')
}
}
}
}Find a specific tool by name.
fun findToolCallback(toolName: String): Mono<ToolCallback>Parameters:
toolName: String - Name of tool to findReturns: Mono<ToolCallback> - Tool callback if found, empty Mono otherwise
Usage:
@Service
class ToolLookupService(
private val toolRegistry: ToolRegistry
) {
fun getTool(toolName: String): Mono<ToolCallback> {
return toolRegistry.findToolCallback(toolName)
.doOnNext { callback ->
logger.info("Found tool: ${callback.name}")
}
.doOnSuccess { callback ->
if (callback == null) {
logger.warn("Tool not found: $toolName")
}
}
}
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 getToolDescription(toolName: String): Mono<String> {
return toolRegistry.findToolCallback(toolName)
.map { it.description }
.defaultIfEmpty("Tool not found")
}
companion object {
private val logger = LoggerFactory.getLogger(ToolLookupService::class.java)
}
}
data class ToolInfo(
val name: String,
val description: String,
val schemaType: String
)Error Handling:
@Service
class SafeLookupService(
private val toolRegistry: ToolRegistry
) {
fun getToolSafely(toolName: String): Mono<ToolCallback?> {
return toolRegistry.findToolCallback(toolName)
.onErrorReturn(null) // Return null on error
}
fun getToolOrDefault(toolName: String, default: ToolCallback): Mono<ToolCallback> {
return toolRegistry.findToolCallback(toolName)
.defaultIfEmpty(default) // Use default if not found
}
fun getToolOrThrow(toolName: String): Mono<ToolCallback> {
return toolRegistry.findToolCallback(toolName)
.switchIfEmpty(
Mono.error(ToolNotFoundException("Tool not found: $toolName"))
)
}
}
class ToolNotFoundException(message: String) : RuntimeException(message)Check if a tool exists.
fun hasToolCallback(toolName: String): Mono<Boolean>Parameters:
toolName: String - Name of tool to checkReturns: Mono<Boolean> - True if tool exists, false otherwise
Usage:
@Service
class ToolExistenceService(
private val toolRegistry: ToolRegistry
) {
fun toolExists(toolName: String): Mono<Boolean> {
return toolRegistry.hasToolCallback(toolName)
}
fun requireTool(toolName: String): Mono<Void> {
return toolRegistry.hasToolCallback(toolName)
.flatMap { exists ->
if (exists) {
Mono.empty()
} else {
Mono.error(IllegalStateException("Required tool missing: $toolName"))
}
}
}
fun checkMultipleTools(toolNames: List<String>): Mono<Map<String, Boolean>> {
return Flux.fromIterable(toolNames)
.flatMap { name ->
toolRegistry.hasToolCallback(name)
.map { exists -> name to exists }
}
.collectMap({ it.first }, { it.second })
}
fun allToolsExist(toolNames: List<String>): Mono<Boolean> {
return Flux.fromIterable(toolNames)
.flatMap { name -> toolRegistry.hasToolCallback(name) }
.all { it }
}
}Synchronous implementation of ToolRegistry.
package com.embabel.agent.mcpserver.sync
class SyncToolRegistry : ToolRegistry {
// Thread-safe storage of tool callbacks
private val tools: ConcurrentMap<String, ToolCallback>
override fun listToolCallbacks(): Mono<List<ToolCallback>>
override fun findToolCallback(toolName: String): Mono<ToolCallback>
override fun hasToolCallback(toolName: String): Mono<Boolean>
}Characteristics:
Auto-Configuration:
# Activates SyncToolRegistry
spring.ai.mcp.server.type=SYNCAsynchronous implementation of ToolRegistry.
package com.embabel.agent.mcpserver.async
class AsyncToolRegistry : ToolRegistry {
// Reactive storage of tool callbacks
private val tools: ConcurrentMap<String, ToolCallback>
override fun listToolCallbacks(): Mono<List<ToolCallback>>
override fun findToolCallback(toolName: String): Mono<ToolCallback>
override fun hasToolCallback(toolName: String): Mono<Boolean>
}Characteristics:
Auto-Configuration:
# Activates AsyncToolRegistry
spring.ai.mcp.server.type=ASYNC@Service
class ToolInspectionService(
private val toolRegistry: ToolRegistry
) {
fun inspectAllTools(): Mono<List<ToolDetails>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.map { callback ->
ToolDetails(
name = callback.name,
description = callback.description,
schemaType = callback.inputTypeSchema?.typeName ?: "unknown",
hasSchema = callback.inputTypeSchema != null
)
}
}
}
fun findToolsWithoutSchema(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks
.filter { it.inputTypeSchema == null }
.map { it.name }
}
}
fun generateToolReport(): Mono<ToolReport> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
ToolReport(
totalTools = callbacks.size,
toolsByPrefix = callbacks.groupBy { it.name.substringBefore('_') }
.mapValues { it.value.size },
toolsWithSchema = callbacks.count { it.inputTypeSchema != null },
toolsWithoutSchema = callbacks.count { it.inputTypeSchema == null }
)
}
}
}
data class ToolDetails(
val name: String,
val description: String,
val schemaType: String,
val hasSchema: Boolean
)
data class ToolReport(
val totalTools: Int,
val toolsByPrefix: Map<String, Int>,
val toolsWithSchema: Int,
val toolsWithoutSchema: Int
)@Service
class ToolValidationService(
private val toolRegistry: ToolRegistry
) {
fun validateToolExists(toolName: String): Mono<ValidationResult> {
return toolRegistry.hasToolCallback(toolName)
.map { exists ->
if (exists) {
ValidationResult.success("Tool exists: $toolName")
} else {
ValidationResult.failure("Tool not found: $toolName")
}
}
}
fun validateAllToolsExist(toolNames: List<String>): Mono<ValidationResult> {
return Flux.fromIterable(toolNames)
.flatMap { name ->
toolRegistry.hasToolCallback(name)
.map { exists -> name to exists }
}
.collectList()
.map { results ->
val missing = results.filter { !it.second }.map { it.first }
if (missing.isEmpty()) {
ValidationResult.success("All tools exist")
} else {
ValidationResult.failure("Missing tools: ${missing.joinToString()}")
}
}
}
fun validateToolSchema(toolName: String): Mono<ValidationResult> {
return toolRegistry.findToolCallback(toolName)
.map { callback ->
if (callback.inputTypeSchema != null) {
ValidationResult.success("Tool has valid schema")
} else {
ValidationResult.failure("Tool missing schema")
}
}
.defaultIfEmpty(ValidationResult.failure("Tool not found"))
}
}
data class ValidationResult(
val valid: Boolean,
val message: String
) {
companion object {
fun success(message: String) = ValidationResult(true, message)
fun failure(message: String) = ValidationResult(false, message)
}
}@Service
class ToolSearchService(
private val toolRegistry: ToolRegistry
) {
fun searchTools(query: String): Mono<List<ToolCallback>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.filter { callback ->
callback.name.contains(query, ignoreCase = true) ||
callback.description.contains(query, ignoreCase = true)
}
}
}
fun searchByName(pattern: String): Mono<List<ToolCallback>> {
val regex = pattern.toRegex(RegexOption.IGNORE_CASE)
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.filter { callback ->
callback.name.matches(regex)
}
}
}
fun searchByDescription(keyword: String): Mono<List<ToolCallback>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.filter { callback ->
callback.description.contains(keyword, ignoreCase = true)
}
}
}
fun autocomplete(prefix: String): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks
.map { it.name }
.filter { it.startsWith(prefix, ignoreCase = true) }
.sorted()
}
}
}@RestController
@RequestMapping("/api/tools")
class ToolRegistryController(
private val toolRegistry: ToolRegistry
) {
@GetMapping
fun listAllTools(): Mono<ResponseEntity<List<ToolSummary>>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.map { callback ->
ToolSummary(callback.name, callback.description)
}
}
.map { ResponseEntity.ok(it) }
}
@GetMapping("/{toolName}")
fun getTool(@PathVariable toolName: String): Mono<ResponseEntity<ToolDetails>> {
return toolRegistry.findToolCallback(toolName)
.map { callback ->
ToolDetails(
name = callback.name,
description = callback.description,
schemaType = callback.inputTypeSchema?.typeName ?: "unknown"
)
}
.map { ResponseEntity.ok(it) }
.defaultIfEmpty(ResponseEntity.notFound().build())
}
@GetMapping("/{toolName}/exists")
fun checkToolExists(@PathVariable toolName: String): Mono<ResponseEntity<ExistsResponse>> {
return toolRegistry.hasToolCallback(toolName)
.map { exists -> ExistsResponse(exists) }
.map { ResponseEntity.ok(it) }
}
@GetMapping("/search")
fun searchTools(@RequestParam query: String): Mono<ResponseEntity<List<ToolSummary>>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks
.filter { it.name.contains(query, ignoreCase = true) }
.map { ToolSummary(it.name, it.description) }
}
.map { ResponseEntity.ok(it) }
}
}
data class ToolSummary(val name: String, val description: String)
data class ToolDetails(val name: String, val description: String, val schemaType: String)
data class ExistsResponse(val exists: Boolean)@Service
class SafeToolManagement(
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")
}
}
}
fun removeToolsWithPrefix(prefix: String): Mono<Int> {
return toolRegistry.listToolCallbacks()
.flatMapMany { Flux.fromIterable(it) }
.filter { callback -> callback.name.startsWith(prefix) }
.flatMap { callback ->
serverStrategy.removeToolCallback(callback.name)
.thenReturn(1)
}
.reduce(0, Int::plus)
.doOnSuccess { count ->
logger.info("Removed $count tools with prefix: $prefix")
}
}
companion object {
private val logger = LoggerFactory.getLogger(SafeToolManagement::class.java)
}
}@Service
class ToolReloadService(
private val serverStrategy: McpServerStrategy,
private val toolRegistry: ToolRegistry
) {
fun reloadTool(toolName: String, newImplementation: ToolCallback): Mono<Void> {
return toolRegistry.hasToolCallback(toolName)
.flatMap { exists ->
if (exists) {
serverStrategy.removeToolCallback(toolName)
} else {
Mono.empty()
}
}
.then(serverStrategy.addToolCallback(newImplementation))
.doOnSuccess { logger.info("Reloaded tool: $toolName") }
}
}Handle Empty Results: Use defaultIfEmpty() or switchIfEmpty()
toolRegistry.findToolCallback(toolName)
.defaultIfEmpty(defaultTool)Use Appropriate Logging: Log registry operations
toolRegistry.listToolCallbacks()
.doOnSubscribe { logger.info("Fetching all tools") }
.doOnNext { tools -> logger.info("Found ${tools.size} tools") }Cache Results: Cache for expensive operations
private val cachedTools: Mono<List<ToolCallback>> by lazy {
toolRegistry.listToolCallbacks().cache()
}Validate Before Use: Check tool existence
toolRegistry.hasToolCallback(toolName)
.filter { it }
.flatMap { useTool(toolName) }Use Reactive Operators: Chain operations efficiently
toolRegistry.listToolCallbacks()
.flatMapMany { Flux.fromIterable(it) }
.filter { /* condition */ }
.map { /* transform */ }
.collectList()