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

tool-registry.mddocs/api/

Tool Registry API

API reference for ToolRegistry interface and implementations for querying and managing registered tools.

Package

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

Overview

ToolRegistry provides a centralized registry for querying registered tools. It enables runtime inspection of available tools and their specifications.

Interface

ToolRegistry { .api }

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:

listToolCallbacks { .api }

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

findToolCallback { .api }

Find a specific tool by name.

fun findToolCallback(toolName: String): Mono<ToolCallback>

Parameters:

  • toolName: String - Name of tool to find

Returns: 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)

hasToolCallback { .api }

Check if a tool exists.

fun hasToolCallback(toolName: String): Mono<Boolean>

Parameters:

  • toolName: String - Name of tool to check

Returns: 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 }
    }
}

Implementations

SyncToolRegistry { .api }

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:

  • Thread-safe concurrent storage
  • Blocking operations wrapped in Mono
  • Simple synchronous access patterns

Auto-Configuration:

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

AsyncToolRegistry { .api }

Asynchronous 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:

  • Native reactive operations
  • Non-blocking access patterns
  • Optimized for high concurrency

Auto-Configuration:

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

Usage Patterns

Tool Inspection { .api }

@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
)

Tool Validation { .api }

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

Tool Search { .api }

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

REST API Integration { .api }

@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)

Usage with ServerStrategy

Safe Tool Removal { .api }

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

Tool Reload { .api }

@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") }
    }
}

Best Practices

  1. Handle Empty Results: Use defaultIfEmpty() or switchIfEmpty()

    toolRegistry.findToolCallback(toolName)
        .defaultIfEmpty(defaultTool)
  2. Use Appropriate Logging: Log registry operations

    toolRegistry.listToolCallbacks()
        .doOnSubscribe { logger.info("Fetching all tools") }
        .doOnNext { tools -> logger.info("Found ${tools.size} tools") }
  3. Cache Results: Cache for expensive operations

    private val cachedTools: Mono<List<ToolCallback>> by lazy {
        toolRegistry.listToolCallbacks().cache()
    }
  4. Validate Before Use: Check tool existence

    toolRegistry.hasToolCallback(toolName)
        .filter { it }
        .flatMap { useTool(toolName) }
  5. Use Reactive Operators: Chain operations efficiently

    toolRegistry.listToolCallbacks()
        .flatMapMany { Flux.fromIterable(it) }
        .filter { /* condition */ }
        .map { /* transform */ }
        .collectList()

See Also

  • Dynamic Tool Management Guide - Runtime tool operations
  • Server Strategy API - Server operations
  • Domain Types API - ToolSpecification
  • Extensions API - Utility functions
tessl i tessl/maven-com-embabel-agent--embabel-agent-mcpserver@0.3.1

docs

index.md

tile.json