Discover and Export available Agent(s) as MCP Servers
Learn how to export Embabel tools as MCP tools using McpToolExport factory methods.
McpToolExport provides factory methods to convert Embabel components into MCP-compatible tool exports. Supports:
ToolObject instancesLlmReference instancesimport com.embabel.agent.mcpserver.McpToolExport
import com.embabel.agent.api.common.ToolObject
// Create tool object with your tool instances
val toolObject = ToolObject(
objects = listOf(calculatorTool, weatherTool, dataTool)
)
// Export to MCP
val export = McpToolExport.fromToolObject(toolObject)
// Use in publisher
@Service
class MyToolPublisher : McpExportToolCallbackPublisher {
override val toolCallbacks = export.toolCallbacks
override fun infoString(verbose: Boolean?, indent: Int) =
"MyToolPublisher: ${toolCallbacks.size} tools"
}val apiTools = ToolObject(objects = listOf(getTool, postTool, deleteTool))
val utilTools = ToolObject(objects = listOf(formatTool, validateTool))
val export = McpToolExport.fromToolObjects(
toolObjects = listOf(apiTools, utilTools)
)Add prefix to avoid naming conflicts:
val export = McpToolExport.fromToolObject(
ToolObject(
objects = listOf(myTool),
namingStrategy = { "api_$it" }
)
)
// Tool "getData" becomes "api_getData"Chain naming strategies:
val apiTools = ToolObject(
objects = listOf(tool1, tool2),
namingStrategy = { "api_$it" }
)
val utilTools = ToolObject(
objects = listOf(tool3, tool4),
namingStrategy = { "util_$it" }
)
// Apply global prefix
val export = McpToolExport.fromToolObjects(
toolObjects = listOf(apiTools, utilTools),
namingStrategy = { "myapp_$it" }
)
// Results in: "myapp_api_tool1", "myapp_util_tool3", etc.Apply transformations:
import com.embabel.agent.api.common.StringTransformer
// Uppercase
val export = McpToolExport.fromToolObject(
ToolObject(
objects = listOf(myTool),
namingStrategy = { it.uppercase() }
)
)
// Lowercase with underscore
val export = McpToolExport.fromToolObject(
ToolObject(
objects = listOf(myTool),
namingStrategy = { it.lowercase().replace(" ", "_") }
)
)
// Custom transformer
val customStrategy = StringTransformer { name ->
"v2_${name.take(20).lowercase()}"
}
val export = McpToolExport.fromToolObject(
ToolObject(
objects = listOf(myTool),
namingStrategy = customStrategy
)
)Preserve original names:
import com.embabel.agent.api.common.StringTransformer
val export = McpToolExport.fromToolObject(
ToolObject(
objects = listOf(myTool),
namingStrategy = StringTransformer.IDENTITY // No transformation
)
)Include only tools matching criteria:
// Only public tools
val export = McpToolExport.fromToolObject(
ToolObject(
objects = listOf(publicTool, internalTool, privateTool),
filter = { toolName -> toolName.startsWith("public_") }
)
)
// Exclude deprecated tools
val export = McpToolExport.fromToolObject(
ToolObject(
objects = toolList,
filter = { toolName -> !toolName.contains("deprecated") }
)
)Combine multiple conditions:
val export = McpToolExport.fromToolObject(
ToolObject(
objects = allTools,
filter = { toolName ->
toolName.length <= 50 && // Name length limit
!toolName.startsWith("_") && // No private tools
!toolName.contains("test") && // No test tools
toolName.matches(Regex("[a-zA-Z0-9_]+")) // Valid characters only
}
)
)Allow only specific tools:
val allowedTools = setOf("getData", "processData", "saveData")
val export = McpToolExport.fromToolObject(
ToolObject(
objects = allTools,
filter = { toolName -> toolName in allowedTools }
)
)Exclude specific tools:
val excludedTools = setOf("dangerousTool", "internalHelper", "debugTool")
val export = McpToolExport.fromToolObject(
ToolObject(
objects = allTools,
filter = { toolName -> toolName !in excludedTools }
)
)LlmReference includes built-in naming strategy:
import com.embabel.agent.api.common.LlmReference
val llmReference: LlmReference = // Obtain from Embabel framework
// Export with automatic prefix from reference name
val export = McpToolExport.fromLlmReference(llmReference)
// If reference named "MyAPI", tools prefixed with "myapi_"Add extra naming transformation:
// Add version prefix
val export = McpToolExport.fromLlmReference(
llmReference = llmReference,
namingStrategy = { "v2_$it" }
)
// Results in: "v2_myapi_toolname"
// Environment-specific
val env = System.getenv("ENV") ?: "dev"
val export = McpToolExport.fromLlmReference(
llmReference = llmReference,
namingStrategy = { "${env}_$it" }
)
// Results in: "prod_myapi_toolname" in productionExport from multiple references:
val authRef: LlmReference = // Authentication reference
val dataRef: LlmReference = // Data access reference
val notifRef: LlmReference = // Notification reference
val export = McpToolExport.fromLlmReferences(
llmReferences = listOf(authRef, dataRef, notifRef),
namingStrategy = { "service_$it" }
)
// Each reference gets its own prefix, plus "service_"Combine ToolObject and LlmReference exports:
@Service
class CombinedToolPublisher : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() {
// Export from ToolObject
val toolObjectExport = McpToolExport.fromToolObject(
ToolObject(
objects = listOf(customTool1, customTool2),
namingStrategy = { "custom_$it" }
)
)
// Export from LlmReference
val llmRefExport = McpToolExport.fromLlmReference(
llmReference = myLlmReference,
namingStrategy = { "llm_$it" }
)
// Combine callbacks
return toolObjectExport.toolCallbacks + llmRefExport.toolCallbacks
}
override fun infoString(verbose: Boolean?, indent: Int) =
"CombinedToolPublisher: ${toolCallbacks.size} tools"
}Different tools for different environments:
@Service
class EnvironmentToolPublisher(
@Value("\${spring.profiles.active:dev}") private val profile: String
) : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() {
val baseTools = ToolObject(
objects = getBaseTools(),
namingStrategy = { "${profile}_$it" }
)
val tools = mutableListOf(McpToolExport.fromToolObject(baseTools))
// Add debug tools in dev
if (profile == "dev") {
val debugTools = ToolObject(
objects = getDebugTools(),
namingStrategy = { "debug_$it" }
)
tools.add(McpToolExport.fromToolObject(debugTools))
}
return tools.flatMap { it.toolCallbacks }
}
override fun infoString(verbose: Boolean?, indent: Int) =
"EnvironmentToolPublisher ($profile): ${toolCallbacks.size} tools"
private fun getBaseTools(): List<Any> = listOf(/* base tools */)
private fun getDebugTools(): List<Any> = listOf(/* debug tools */)
}Tools with identical names (after transformation) are automatically deduplicated:
val tools1 = ToolObject(
objects = listOf(tool1),
namingStrategy = { "api_getData" } // Always returns same name
)
val tools2 = ToolObject(
objects = listOf(tool2),
namingStrategy = { "api_getData" } // Same name
)
val export = McpToolExport.fromToolObjects(
toolObjects = listOf(tools1, tools2)
)
// Only one "api_getData" tool will be exportedAll exported tools are automatically logged at INFO level:
[INFO] McpToolExport - Exporting tool: myapp_api_getData
[INFO] McpToolExport - Exporting tool: myapp_api_postData
[INFO] McpToolExport - Total tools exported: 2// Good: Clear namespace
namingStrategy = { "payment_api_$it" }
// Bad: Cryptic abbreviation
namingStrategy = { "pa_$it" }// Good: Concise but clear
namingStrategy = { "user_$it" }
// Bad: Unnecessarily verbose
namingStrategy = { "user_management_service_$it" }// Good: Filter at ToolObject level
val toolObject = ToolObject(
objects = allTools,
filter = { !it.startsWith("_") }
)
// Less efficient: Export then filter elsewhere@Service
class ApiToolPublisher : McpExportToolCallbackPublisher {
/**
* Exports API tools with "api_" prefix to avoid conflicts
* with utility tools exported by other publishers.
*/
override val toolCallbacks: List<ToolCallback>
get() = McpToolExport.fromToolObject(
ToolObject(
objects = apiTools,
namingStrategy = { "api_$it" }
)
).toolCallbacks
// ...
}// Use hierarchical prefixes
val userApiTools = ToolObject(
objects = userTools,
namingStrategy = { "api_user_$it" }
)
val adminApiTools = ToolObject(
objects = adminTools,
namingStrategy = { "api_admin_$it" }
)@Service
class ModularToolPublisher : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() {
val modules = mapOf(
"auth" to authTools,
"data" to dataTools,
"report" to reportTools
)
return modules.flatMap { (moduleName, tools) ->
McpToolExport.fromToolObject(
ToolObject(
objects = tools,
namingStrategy = { "${moduleName}_$it" }
)
).toolCallbacks
}
}
override fun infoString(verbose: Boolean?, indent: Int) =
"ModularToolPublisher: ${toolCallbacks.size} tools"
}@Service
class VersionedToolPublisher : McpExportToolCallbackPublisher {
override val toolCallbacks: List<ToolCallback>
get() {
val v1Tools = McpToolExport.fromToolObject(
ToolObject(
objects = getV1Tools(),
namingStrategy = { "v1_$it" }
)
)
val v2Tools = McpToolExport.fromToolObject(
ToolObject(
objects = getV2Tools(),
namingStrategy = { "v2_$it" }
)
)
return v1Tools.toolCallbacks + v2Tools.toolCallbacks
}
override fun infoString(verbose: Boolean?, indent: Int) =
"VersionedToolPublisher: ${toolCallbacks.size} tools"
}Cause: Empty tool list or all filtered out
Solution: Verify objects list is non-empty and filter allows some tools
val objects = listOf(tool1, tool2, tool3)
println("Object count: ${objects.size}")
val filtered = objects.filter { /* filter logic */ }
println("After filtering: ${filtered.size}")Cause: Multiple tools produce same name after transformation
Solution: Use more specific naming strategies or check for duplicates
val names = mutableSetOf<String>()
objects.forEach { tool ->
val name = namingStrategy(tool.name)
if (name in names) {
println("WARNING: Duplicate name: $name")
}
names.add(name)
}Note: LlmReference prompt elements are not exported by McpToolExport. Use prompt publishers for prompts.