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.