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

exporting-tools.mddocs/guides/

Exporting Tools to MCP

Learn how to export Embabel tools as MCP tools using McpToolExport factory methods.

Overview

McpToolExport provides factory methods to convert Embabel components into MCP-compatible tool exports. Supports:

  • Single or multiple ToolObject instances
  • Single or multiple LlmReference instances
  • Naming strategies for namespacing
  • Filtering tools before export

Basic Tool Export

From Single ToolObject

import 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"
}

From Multiple ToolObjects

val apiTools = ToolObject(objects = listOf(getTool, postTool, deleteTool))
val utilTools = ToolObject(objects = listOf(formatTool, validateTool))

val export = McpToolExport.fromToolObjects(
    toolObjects = listOf(apiTools, utilTools)
)

Naming Strategies

Prefix Strategy

Add prefix to avoid naming conflicts:

val export = McpToolExport.fromToolObject(
    ToolObject(
        objects = listOf(myTool),
        namingStrategy = { "api_$it" }
    )
)
// Tool "getData" becomes "api_getData"

Multiple Prefixes

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.

Transform Strategies

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

Identity Strategy

Preserve original names:

import com.embabel.agent.api.common.StringTransformer

val export = McpToolExport.fromToolObject(
    ToolObject(
        objects = listOf(myTool),
        namingStrategy = StringTransformer.IDENTITY  // No transformation
    )
)

Filtering Tools

Basic Filtering

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

Complex Filtering

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

Whitelist Filtering

Allow only specific tools:

val allowedTools = setOf("getData", "processData", "saveData")

val export = McpToolExport.fromToolObject(
    ToolObject(
        objects = allTools,
        filter = { toolName -> toolName in allowedTools }
    )
)

Blacklist Filtering

Exclude specific tools:

val excludedTools = setOf("dangerousTool", "internalHelper", "debugTool")

val export = McpToolExport.fromToolObject(
    ToolObject(
        objects = allTools,
        filter = { toolName -> toolName !in excludedTools }
    )
)

LlmReference Export

Basic LlmReference Export

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_"

LlmReference with Additional Strategy

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 production

Multiple LlmReferences

Export 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_"

Combining Export Strategies

Mixed Sources

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

Environment-Based Export

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 */)
}

Deduplication

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 exported

Logging

All 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

Best Practices

1. Use Descriptive Prefixes

// Good: Clear namespace
namingStrategy = { "payment_api_$it" }

// Bad: Cryptic abbreviation
namingStrategy = { "pa_$it" }

2. Keep Names Short

// Good: Concise but clear
namingStrategy = { "user_$it" }

// Bad: Unnecessarily verbose
namingStrategy = { "user_management_service_$it" }

3. Filter Early

// Good: Filter at ToolObject level
val toolObject = ToolObject(
    objects = allTools,
    filter = { !it.startsWith("_") }
)

// Less efficient: Export then filter elsewhere

4. Document Naming Strategy

@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

    // ...
}

5. Handle Name Collisions

// Use hierarchical prefixes
val userApiTools = ToolObject(
    objects = userTools,
    namingStrategy = { "api_user_$it" }
)

val adminApiTools = ToolObject(
    objects = adminTools,
    namingStrategy = { "api_admin_$it" }
)

Common Patterns

Module-Based Organization

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

Version-Based Export

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

Troubleshooting

No Tools Exported

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

Name Conflicts

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

LlmReference Prompts Missing

Note: LlmReference prompt elements are not exported by McpToolExport. Use prompt publishers for prompts.

Related Documentation

  • Tool Export API - Complete API reference
  • Creating Publishers - Publisher patterns
  • Getting Started - Basic setup
tessl i tessl/maven-com-embabel-agent--embabel-agent-mcpserver@0.3.1

docs

index.md

tile.json