Discover and Export available Agent(s) as MCP Servers
API reference for Kotlin extension functions that provide convenient utility methods for working with MCP server components.
com.embabel.agent.mcpserver.extensionsExtension functions add utility methods to existing types without modifying their source code. These extensions simplify common operations on tool callbacks, registries, and publishers.
Extract tool names from a collection of tool callbacks.
package com.embabel.agent.mcpserver.extensions
fun List<ToolCallback>.toolNames(): List<String> {
return this.map { it.name }
}Receiver: List<ToolCallback>
Returns: List<String> - List of tool names
Usage:
import com.embabel.agent.mcpserver.extensions.toolNames
import org.springframework.ai.tool.ToolCallback
@Service
class ToolNameService(
private val toolRegistry: ToolRegistry
) {
fun getAllToolNames(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks -> callbacks.toolNames() }
}
fun printToolNames() {
toolRegistry.listToolCallbacks()
.subscribe { callbacks ->
val names = callbacks.toolNames()
println("Registered tools: ${names.joinToString()}")
}
}
fun countToolsWithPrefix(prefix: String): Mono<Int> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.toolNames()
.count { it.startsWith(prefix) }
}
}
}Combined with Filtering:
@Service
class FilteredToolNameService(
private val toolRegistry: ToolRegistry
) {
fun getApiToolNames(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks
.filter { it.name.startsWith("api_") }
.toolNames()
}
}
fun getToolNamesByPattern(pattern: Regex): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks
.filter { it.name.matches(pattern) }
.toolNames()
}
}
fun getSortedToolNames(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.toolNames().sorted()
}
}
}Publisher Usage:
@Service
class MyToolPublisher : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() = McpToolExport.fromToolObject(
ToolObject(
objects = listOf(tool1, tool2, tool3),
namingStrategy = { "api_$it" }
)
).toolCallbacks
override fun infoString(verbose: Boolean?, indent: Int): String {
val names = toolCallbacks.toolNames()
return if (verbose == true) {
"MyToolPublisher: ${toolCallbacks.size} tools\n - ${names.joinToString("\n - ")}"
} else {
"MyToolPublisher: ${toolCallbacks.size} tools"
}
}
private val tool1: Any = TODO()
private val tool2: Any = TODO()
private val tool3: Any = TODO()
}Filter tool callbacks by name prefix.
fun List<ToolCallback>.filterByPrefix(prefix: String): List<ToolCallback> {
return this.filter { it.name.startsWith(prefix) }
}Receiver: List<ToolCallback>
Parameters:
prefix: String - Prefix to matchReturns: List<ToolCallback> - Filtered list
Usage:
import com.embabel.agent.mcpserver.extensions.filterByPrefix
@Service
class PrefixFilterService(
private val toolRegistry: ToolRegistry
) {
fun getApiTools(): Mono<List<ToolCallback>> {
return toolRegistry.listToolCallbacks()
.map { callbacks -> callbacks.filterByPrefix("api_") }
}
fun getUtilityTools(): Mono<List<ToolCallback>> {
return toolRegistry.listToolCallbacks()
.map { callbacks -> callbacks.filterByPrefix("util_") }
}
fun countToolsByPrefix(prefix: String): Mono<Int> {
return toolRegistry.listToolCallbacks()
.map { callbacks -> callbacks.filterByPrefix(prefix).size }
}
}Group tool callbacks by their name prefix (up to first underscore).
fun List<ToolCallback>.groupByPrefix(): Map<String, List<ToolCallback>> {
return this.groupBy { callback ->
callback.name.substringBefore('_')
}
}Receiver: List<ToolCallback>
Returns: Map<String, List<ToolCallback>> - Tools grouped by prefix
Usage:
import com.embabel.agent.mcpserver.extensions.groupByPrefix
@Service
class ToolGroupingService(
private val toolRegistry: ToolRegistry
) {
fun getToolsByModule(): Mono<Map<String, List<ToolCallback>>> {
return toolRegistry.listToolCallbacks()
.map { callbacks -> callbacks.groupByPrefix() }
}
fun countToolsByModule(): Mono<Map<String, Int>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.groupByPrefix()
.mapValues { (_, tools) -> tools.size }
}
}
fun printModuleSummary() {
toolRegistry.listToolCallbacks()
.subscribe { callbacks ->
val grouped = callbacks.groupByPrefix()
println("Tools by module:")
grouped.forEach { (prefix, tools) ->
println(" $prefix: ${tools.size} tools")
}
}
}
}Check if a tool with given name exists in the list.
fun List<ToolCallback>.hasToolNamed(toolName: String): Boolean {
return this.any { it.name == toolName }
}Receiver: List<ToolCallback>
Parameters:
toolName: String - Tool name to checkReturns: Boolean - True if tool exists
Usage:
import com.embabel.agent.mcpserver.extensions.hasToolNamed
@Service
class ToolExistenceService(
private val toolRegistry: ToolRegistry
) {
fun checkToolExists(toolName: String): Mono<Boolean> {
return toolRegistry.listToolCallbacks()
.map { callbacks -> callbacks.hasToolNamed(toolName) }
}
fun checkMultipleToolsExist(toolNames: List<String>): Mono<Map<String, Boolean>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
toolNames.associateWith { name ->
callbacks.hasToolNamed(name)
}
}
}
fun allToolsExist(toolNames: List<String>): Mono<Boolean> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
toolNames.all { name -> callbacks.hasToolNamed(name) }
}
}
}Sanitize string to valid tool name format.
fun String.toToolName(): String {
return this
.replace(Regex("[^a-zA-Z0-9_]"), "_")
.toLowerCase()
.take(50)
}Receiver: String
Returns: String - Sanitized tool name
Usage:
import com.embabel.agent.mcpserver.extensions.toToolName
// Sanitize user input
val userInput = "My API Tool!"
val toolName = userInput.toToolName() // "my_api_tool_"
// Generate tool names
val displayName = "User Management (Admin)"
val toolName = displayName.toToolName() // "user_management__admin_"
// Validate and sanitize
fun createToolName(input: String): String {
val sanitized = input.toToolName()
require(sanitized.isNotBlank()) { "Invalid tool name" }
return sanitized
}Check if string is a prefix of tool name.
fun String.isPrefixOf(toolName: String): Boolean {
return toolName.startsWith(this)
}Receiver: String
Parameters:
toolName: String - Tool name to checkReturns: Boolean - True if receiver is prefix
Usage:
import com.embabel.agent.mcpserver.extensions.isPrefixOf
@Service
class PrefixCheckService {
fun findToolsWithPrefix(prefix: String): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks
.filter { prefix.isPrefixOf(it.name) }
.map { it.name }
}
}
fun hasToolsWithPrefix(prefix: String): Mono<Boolean> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.any { prefix.isPrefixOf(it.name) }
}
}
}Get number of tools from publisher.
val McpExportToolCallbackPublisher.toolCount: Int
get() = this.toolCallbacks.sizeReceiver: McpExportToolCallbackPublisher
Returns: Int - Number of tools
Usage:
import com.embabel.agent.mcpserver.extensions.toolCount
@Service
class PublisherAnalyzer(
private val publishers: List<McpExportToolCallbackPublisher>
) {
fun countAllTools(): Int {
return publishers.sumOf { it.toolCount }
}
fun findLargestPublisher(): McpExportToolCallbackPublisher? {
return publishers.maxByOrNull { it.toolCount }
}
fun printPublisherSummary() {
publishers.forEach { publisher ->
println("${publisher::class.simpleName}: ${publisher.toolCount} tools")
}
}
fun hasEmptyPublishers(): Boolean {
return publishers.any { it.toolCount == 0 }
}
}Get number of resources from publisher.
val McpResourcePublisher.resourceCount: Int
get() = this.resources().sizeReceiver: McpResourcePublisher
Returns: Int - Number of resources
Usage:
import com.embabel.agent.mcpserver.extensions.resourceCount
@Service
class ResourceAnalyzer(
private val publishers: List<McpResourcePublisher>
) {
fun countAllResources(): Int {
return publishers.sumOf { it.resourceCount }
}
fun findPublisherWithMostResources(): McpResourcePublisher? {
return publishers.maxByOrNull { it.resourceCount }
}
fun hasResourcePublishers(): Boolean {
return publishers.any { it.resourceCount > 0 }
}
}Extract tool names from Mono of tool callbacks.
fun Mono<List<ToolCallback>>.extractNames(): Mono<List<String>> {
return this.map { callbacks -> callbacks.toolNames() }
}Receiver: Mono<List<ToolCallback>>
Returns: Mono<List<String>> - Mono of tool names
Usage:
import com.embabel.agent.mcpserver.extensions.extractNames
@Service
class ReactiveToolService(
private val toolRegistry: ToolRegistry
) {
fun getToolNames(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.extractNames()
}
fun printToolNames() {
toolRegistry.listToolCallbacks()
.extractNames()
.subscribe { names ->
println("Tools: ${names.joinToString()}")
}
}
}Convert Flux of tool callbacks to list of names.
fun Flux<ToolCallback>.toNamesList(): Mono<List<String>> {
return this.map { it.name }.collectList()
}Receiver: Flux<ToolCallback>
Returns: Mono<List<String>> - Mono of tool names
Usage:
import com.embabel.agent.mcpserver.extensions.toNamesList
@Service
class FluxToolService(
private val toolRegistry: ToolRegistry
) {
fun getFilteredToolNames(prefix: String): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.flatMapMany { Flux.fromIterable(it) }
.filter { it.name.startsWith(prefix) }
.toNamesList()
}
fun getSortedToolNames(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.flatMapMany { Flux.fromIterable(it) }
.sort { a, b -> a.name.compareTo(b.name) }
.toNamesList()
}
}Check if tool callback is valid (has name and description).
fun ToolCallback.isValid(): Boolean {
return this.name.isNotBlank() && this.description.isNotBlank()
}Receiver: ToolCallback
Returns: Boolean - True if valid
Usage:
import com.embabel.agent.mcpserver.extensions.isValid
@Service
class ValidationService(
private val toolRegistry: ToolRegistry
) {
fun validateAllTools(): Mono<ValidationResult> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
val invalid = callbacks.filterNot { it.isValid() }
if (invalid.isEmpty()) {
ValidationResult.success("All tools valid")
} else {
ValidationResult.failure("Invalid tools: ${invalid.map { it.name }}")
}
}
}
fun findInvalidTools(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks
.filterNot { it.isValid() }
.map { it.name }
}
}
}
data class ValidationResult(val valid: Boolean, val message: String) {
companion object {
fun success(msg: String) = ValidationResult(true, msg)
fun failure(msg: String) = ValidationResult(false, msg)
}
}Check if tool has input schema defined.
fun ToolCallback.hasSchema(): Boolean {
return this.inputTypeSchema != null
}Receiver: ToolCallback
Returns: Boolean - True if schema exists
Usage:
import com.embabel.agent.mcpserver.extensions.hasSchema
@Service
class SchemaAnalyzer(
private val toolRegistry: ToolRegistry
) {
fun countToolsWithSchema(): Mono<Int> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks.count { it.hasSchema() }
}
}
fun findToolsWithoutSchema(): Mono<List<String>> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
callbacks
.filterNot { it.hasSchema() }
.map { it.name }
}
}
fun getSchemaCompleteness(): Mono<Double> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
if (callbacks.isEmpty()) 0.0
else callbacks.count { it.hasSchema() }.toDouble() / callbacks.size
}
}
}import com.embabel.agent.mcpserver.extensions.*
@Service
class ToolAnalysisService(
private val toolRegistry: ToolRegistry
) {
fun generateToolReport(): Mono<ToolReport> {
return toolRegistry.listToolCallbacks()
.map { callbacks ->
ToolReport(
totalTools = callbacks.size,
toolNames = callbacks.toolNames(),
byPrefix = callbacks.groupByPrefix()
.mapValues { it.value.size },
validTools = callbacks.count { it.isValid() },
invalidTools = callbacks.filterNot { it.isValid() }.size,
withSchema = callbacks.count { it.hasSchema() },
withoutSchema = callbacks.filterNot { it.hasSchema() }.size
)
}
}
fun printDetailedReport() {
generateToolReport()
.subscribe { report ->
println("=== Tool Report ===")
println("Total: ${report.totalTools}")
println("\nBy Prefix:")
report.byPrefix.forEach { (prefix, count) ->
println(" $prefix: $count")
}
println("\nValidation:")
println(" Valid: ${report.validTools}")
println(" Invalid: ${report.invalidTools}")
println("\nSchema:")
println(" With schema: ${report.withSchema}")
println(" Without schema: ${report.withoutSchema}")
}
}
}
data class ToolReport(
val totalTools: Int,
val toolNames: List<String>,
val byPrefix: Map<String, Int>,
val validTools: Int,
val invalidTools: Int,
val withSchema: Int,
val withoutSchema: Int
)import com.embabel.agent.mcpserver.extensions.*
@Service
class EnhancedPublisher : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() = McpToolExport.fromToolObject(
ToolObject(
objects = listOf(tool1, tool2, tool3),
namingStrategy = { "api_$it" }
)
).toolCallbacks
override fun infoString(verbose: Boolean?, indent: Int): String {
val names = toolCallbacks.toolNames()
val grouped = toolCallbacks.groupByPrefix()
val valid = toolCallbacks.count { it.isValid() }
val withSchema = toolCallbacks.count { it.hasSchema() }
return if (verbose == true) {
buildString {
appendLine("EnhancedPublisher:")
appendLine(" Total: ${toolCallbacks.size}")
appendLine(" Valid: $valid")
appendLine(" With schema: $withSchema")
appendLine(" By prefix:")
grouped.forEach { (prefix, tools) ->
appendLine(" $prefix: ${tools.size}")
}
appendLine(" Tools:")
names.forEach { name ->
appendLine(" - $name")
}
}
} else {
"EnhancedPublisher: ${toolCallbacks.size} tools"
}
}
private val tool1: Any = TODO()
private val tool2: Any = TODO()
private val tool3: Any = TODO()
}Import Extensions: Import specific extensions you need
import com.embabel.agent.mcpserver.extensions.toolNames
import com.embabel.agent.mcpserver.extensions.filterByPrefixChain Extensions: Combine multiple extensions
callbacks
.filterByPrefix("api_")
.filter { it.hasSchema() }
.toolNames()Use with Reactive Streams: Integrate with Mono/Flux
toolRegistry.listToolCallbacks()
.map { it.toolNames() }
.subscribe { names -> println(names) }Validate Before Use: Check validity with extensions
if (callback.isValid() && callback.hasSchema()) {
useTool(callback)
}Enhanced Logging: Use extensions for better info strings
override fun infoString(verbose: Boolean?, indent: Int): String {
val names = toolCallbacks.toolNames()
return "Publisher: ${names.joinToString()}"
}