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

embabel-integration.mddocs/advanced/

Embabel Integration

Comprehensive guide to integrating embabel-agent-mcpserver with the Embabel Agent Framework, covering automatic agent discovery, goal exposure, and advanced integration patterns.

Table of Contents

Overview

Embabel-agent-mcpserver seamlessly integrates with the Embabel Agent Framework to automatically expose AI agents and their goals as MCP tools, enabling Claude Desktop and other MCP clients to interact with Embabel agents without manual configuration.

Integration Architecture

┌─────────────────────────────────────────────────────────┐
│              Embabel Agent Framework                     │
│                                                          │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐       │
│  │  @Agent    │  │  @Agent    │  │  @Agent    │       │
│  │  UserAgent │  │  DataAgent │  │  PayAgent  │       │
│  └──────┬─────┘  └──────┬─────┘  └──────┬─────┘       │
│         │                │                │             │
│         │ @Goal methods  │                │             │
│         ▼                ▼                ▼             │
│  ┌──────────────────────────────────────────────┐      │
│  │         LlmReference Registry                │      │
│  │  (Auto-discovered agents & goals)            │      │
│  └──────────────────┬───────────────────────────┘      │
└─────────────────────┼───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│         Embabel Agent MCP Server                         │
│                                                          │
│  ┌──────────────────────────────────────────────┐      │
│  │  McpToolExport.fromLlmReference()            │      │
│  │  - Converts agents to MCP tools              │      │
│  │  - Applies naming strategies                 │      │
│  │  - Generates tool schemas                    │      │
│  └──────────────────┬───────────────────────────┘      │
│                     │                                   │
│                     ▼                                   │
│  ┌──────────────────────────────────────────────┐      │
│  │  MCP Server (Sync/Async)                     │      │
│  │  - Exposes tools via SSE                     │      │
│  │  - Handles tool invocations                  │      │
│  └──────────────────────────────────────────────┘      │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│                MCP Clients                               │
│         (Claude Desktop, Custom Clients)                 │
└─────────────────────────────────────────────────────────┘

Key Integration Points

  1. Agent Discovery: Embabel framework auto-discovers @Agent annotated classes
  2. Goal Exposure: @Goal methods automatically become MCP tools
  3. LlmReference: Bridge between Embabel and MCP server
  4. Tool Conversion: McpToolExport.fromLlmReference() handles conversion
  5. Naming Strategy: Automatic namespacing by agent name
  6. Schema Generation: Input types become tool schemas

Embabel Agent Framework

Agent Definition

Embabel agents are defined using @Agent and @Goal annotations: { .api }

import com.embabel.agent.api.common.Agent
import com.embabel.agent.api.common.Goal

@Agent
class UserManagementAgent {

    @Goal(
        name = "createUser",
        description = "Create a new user account with the provided information"
    )
    fun createUser(input: CreateUserInput): CreateUserOutput {
        // Agent implementation
        val userId = userService.createUser(
            email = input.email,
            name = input.name,
            role = input.role
        )

        return CreateUserOutput(
            userId = userId,
            success = true,
            message = "User created successfully"
        )
    }

    @Goal(
        name = "deleteUser",
        description = "Delete an existing user account by ID"
    )
    fun deleteUser(input: DeleteUserInput): DeleteUserOutput {
        userService.deleteUser(input.userId)

        return DeleteUserOutput(
            success = true,
            message = "User deleted successfully"
        )
    }

    @Goal(
        name = "updateUserRole",
        description = "Update the role of an existing user"
    )
    fun updateUserRole(input: UpdateRoleInput): UpdateRoleOutput {
        userService.updateRole(input.userId, input.newRole)

        return UpdateRoleOutput(
            userId = input.userId,
            newRole = input.newRole,
            success = true
        )
    }
}

// Input/Output types
data class CreateUserInput(
    val email: String,
    val name: String,
    val role: String = "user"
)

data class CreateUserOutput(
    val userId: String,
    val success: Boolean,
    val message: String
)

data class DeleteUserInput(val userId: String)
data class DeleteUserOutput(val success: Boolean, val message: String)

data class UpdateRoleInput(val userId: String, val newRole: String)
data class UpdateRoleOutput(val userId: String, val newRole: String, val success: Boolean)

Agent Configuration

Enable Embabel agent auto-discovery: { .api }

# application.properties

# Enable Embabel agent framework
embabel.agent.enabled=true

# Package to scan for agents
embabel.agent.package-scan=com.example.agents

# Auto-discovery of agents
embabel.agent.auto-discovery=true
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.ComponentScan

@SpringBootApplication
@ComponentScan(basePackages = [
    "com.example.agents",  // Your agent packages
    "com.embabel.agent"    // Embabel framework
])
class Application

Automatic Agent Discovery

Discovery Process

  1. Spring Context Initialization: Application starts
  2. Component Scanning: Embabel framework scans for @Agent classes
  3. Agent Registration: Agents registered in internal registry
  4. LlmReference Creation: Framework creates LlmReference for each agent
  5. Bean Injection: LlmReference beans available for injection

Accessing LlmReferences

Auto-Injection: { .api }

import com.embabel.agent.api.common.LlmReference
import org.springframework.stereotype.Service

@Service
class AgentRegistry(
    // All discovered LlmReferences auto-injected
    private val llmReferences: List<LlmReference>
) {

    fun listAllAgents(): List<String> {
        return llmReferences.map { it.name }
    }

    fun findAgent(name: String): LlmReference? {
        return llmReferences.find { it.name == name }
    }

    fun getAgentCount(): Int {
        return llmReferences.size
    }

    init {
        println("Discovered ${llmReferences.size} agents:")
        llmReferences.forEach { ref ->
            println("  - ${ref.name}: ${ref.goals.size} goals")
        }
    }
}

Publisher Integration: { .api }

import com.embabel.agent.mcpserver.McpExportToolCallbackPublisher
import com.embabel.agent.mcpserver.McpToolExport

@Service
class EmbabelAgentPublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback>
        get() = llmReferences.flatMap { reference ->
            McpToolExport.fromLlmReference(reference).toolCallbacks
        }

    override fun infoString(verbose: Boolean?, indent: Int): String {
        return "EmbabelAgentPublisher: ${toolCallbacks.size} tools from " +
               "${llmReferences.size} agents"
    }
}

Selective Agent Exposure

Filter by Agent Name: { .api }

@Service
class FilteredAgentPublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    private val exposedAgents = setOf("UserManagementAgent", "DataAgent")

    override val toolCallbacks: List<ToolCallback>
        get() = llmReferences
            .filter { it.name in exposedAgents }
            .flatMap { reference ->
                McpToolExport.fromLlmReference(reference).toolCallbacks
            }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "FilteredAgentPublisher: ${toolCallbacks.size} tools"
}

Filter by Goal Prefix: { .api }

@Service
class PublicApiPublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback>
        get() = llmReferences.flatMap { reference ->
            val toolObject = ToolObject(
                objects = reference.goals,
                namingStrategy = { "${reference.name.lowercase()}_$it" },
                filter = { goalName ->
                    // Only expose goals starting with "public"
                    goalName.startsWith("public")
                }
            )
            McpToolExport.fromToolObject(toolObject).toolCallbacks
        }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "PublicApiPublisher: ${toolCallbacks.size} public tools"
}

Goal Tool Conversion

Conversion Process

LlmReference to MCP Tools: { .api }

// LlmReference structure (conceptual)
interface LlmReference {
    val name: String                    // Agent name
    val description: String             // Agent description
    val goals: List<Goal>              // List of @Goal methods
}

interface Goal {
    val name: String                    // Goal method name
    val description: String             // @Goal description
    val inputType: Class<*>            // Input parameter type
    val outputType: Class<*>           // Return type
    fun invoke(input: Any): Any        // Execute goal
}

// Conversion to MCP tool
McpToolExport.fromLlmReference(llmReference)
// Creates ToolCallback for each goal:
// - Tool name: "${agentName.lowercase()}_${goalName}"
// - Tool description: From @Goal annotation
// - Tool schema: From input type fields
// - Tool execution: Invokes goal method

Tool Naming

Default Naming Strategy:

Agent Name: UserManagementAgent
Goal Name: createUser
Result: usermanagementagent_createUser

Custom Naming Strategy: { .api }

@Service
class CustomNamedAgentPublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback>
        get() = llmReferences.flatMap { reference ->
            McpToolExport.fromLlmReference(
                llmReference = reference,
                namingStrategy = { goalName ->
                    // Custom format: api_v2_<agent>_<goal>
                    "api_v2_${reference.name.lowercase()}_$goalName"
                }
            ).toolCallbacks
        }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "CustomNamedAgentPublisher: ${toolCallbacks.size} tools"
}

Namespace Collision Prevention: { .api }

@Service
class NamespaceAwarePublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback>
        get() {
            val toolsByName = mutableMapOf<String, ToolCallback>()

            llmReferences.forEach { reference ->
                val agentPrefix = generateUniquePrefix(reference)

                McpToolExport.fromLlmReference(
                    llmReference = reference,
                    namingStrategy = { goalName ->
                        "${agentPrefix}_$goalName"
                    }
                ).toolCallbacks.forEach { tool ->
                    if (toolsByName.containsKey(tool.name)) {
                        logger.warn("Duplicate tool name: ${tool.name}")
                    } else {
                        toolsByName[tool.name] = tool
                    }
                }
            }

            return toolsByName.values.toList()
        }

    private fun generateUniquePrefix(reference: LlmReference): String {
        // Use package name + class name for uniqueness
        return "${reference.packageName}_${reference.simpleName}".lowercase()
    }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "NamespaceAwarePublisher: ${toolCallbacks.size} tools"

    companion object {
        private val logger = LoggerFactory.getLogger(NamespaceAwarePublisher::class.java)
    }
}

Schema Generation

Input Type to JSON Schema: { .api }

// Input type with annotations
data class CreateTaskInput(
    @JsonPropertyDescription("Task title (required, max 100 characters)")
    val title: String,

    @JsonPropertyDescription("Detailed task description")
    val description: String,

    @JsonPropertyDescription("Priority level: 1 (low) to 5 (high)")
    val priority: Int = 3,

    @JsonPropertyDescription("Assigned user ID (optional)")
    val assigneeId: String? = null,

    @JsonPropertyDescription("Due date in ISO-8601 format")
    val dueDate: String? = null
)

// Generated JSON schema (conceptual)
{
  "type": "object",
  "properties": {
    "title": {
      "type": "string",
      "description": "Task title (required, max 100 characters)"
    },
    "description": {
      "type": "string",
      "description": "Detailed task description"
    },
    "priority": {
      "type": "integer",
      "description": "Priority level: 1 (low) to 5 (high)",
      "default": 3
    },
    "assigneeId": {
      "type": "string",
      "description": "Assigned user ID (optional)"
    },
    "dueDate": {
      "type": "string",
      "description": "Due date in ISO-8601 format"
    }
  },
  "required": ["title", "description"]
}

Best Practices for Input Types: { .api }

import com.fasterxml.jackson.annotation.JsonPropertyDescription
import javax.validation.constraints.*

data class WellDocumentedInput(
    @JsonPropertyDescription("User email address (must be valid email format)")
    @Email
    val email: String,

    @JsonPropertyDescription("User age (must be between 18 and 120)")
    @Min(18)
    @Max(120)
    val age: Int,

    @JsonPropertyDescription("User role: 'admin', 'user', or 'viewer'")
    @Pattern(regexp = "^(admin|user|viewer)$")
    val role: String = "user",

    @JsonPropertyDescription("Optional phone number in E.164 format")
    val phoneNumber: String? = null,

    @JsonPropertyDescription("Tags for categorization (max 10 tags)")
    @Size(max = 10)
    val tags: List<String> = emptyList(),

    @JsonPropertyDescription("Metadata as key-value pairs")
    val metadata: Map<String, String> = emptyMap()
)

LlmReference Integration

LlmReference Structure

Core Interface: { .api }

interface LlmReference {
    val name: String
    val description: String
    val goals: List<GoalDefinition>
    val packageName: String
    val simpleName: String
}

interface GoalDefinition {
    val name: String
    val description: String
    val inputType: Class<*>
    val outputType: Class<*>
    fun invoke(input: Any): Any
}

Multiple LlmReference Export

Combine Multiple Agents: { .api }

@Service
class MultiAgentPublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback>
        get() = McpToolExport.fromLlmReferences(
            llmReferences = llmReferences,
            namingStrategy = { toolName ->
                // Global versioning prefix
                "v1_$toolName"
            }
        ).toolCallbacks

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "MultiAgentPublisher: ${toolCallbacks.size} tools from " +
        "${llmReferences.size} agents"
}

Per-Agent Customization: { .api }

@Service
class CustomizedMultiAgentPublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    private val agentConfigs = mapOf(
        "UserAgent" to AgentConfig(prefix = "user", expose = true),
        "AdminAgent" to AgentConfig(prefix = "admin", expose = true),
        "InternalAgent" to AgentConfig(prefix = "internal", expose = false)
    )

    override val toolCallbacks: List<ToolCallback>
        get() = llmReferences
            .mapNotNull { reference ->
                val config = agentConfigs[reference.simpleName]
                if (config?.expose == true) {
                    reference to config
                } else {
                    null
                }
            }
            .flatMap { (reference, config) ->
                McpToolExport.fromLlmReference(
                    llmReference = reference,
                    namingStrategy = { goalName ->
                        "${config.prefix}_$goalName"
                    }
                ).toolCallbacks
            }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "CustomizedMultiAgentPublisher: ${toolCallbacks.size} tools"

    data class AgentConfig(val prefix: String, val expose: Boolean)
}

Autonomy and Agent Behavior

Autonomy Levels

Embabel agents support different autonomy levels that control their behavior:

Autonomy Configuration: { .api }

@Agent
class AutomatedAgent {

    @Goal(
        name = "processData",
        description = "Process data with configurable autonomy",
        autonomy = Autonomy.SUPERVISED  // Requires confirmation
    )
    fun processData(input: ProcessInput): ProcessOutput {
        // Agent implementation
        return ProcessOutput(result = "processed")
    }

    @Goal(
        name = "analyzeData",
        description = "Analyze data autonomously",
        autonomy = Autonomy.AUTONOMOUS  // Runs without confirmation
    )
    fun analyzeData(input: AnalyzeInput): AnalyzeOutput {
        // Agent implementation
        return AnalyzeOutput(insights = listOf())
    }
}

Agent State Management

Stateful Agents: { .api }

@Agent
@Scope("prototype")  // New instance per invocation
class StatefulAgent {

    private val sessionState = mutableMapOf<String, Any>()

    @Goal(name = "initializeSession", description = "Initialize agent session")
    fun initializeSession(input: InitInput): InitOutput {
        sessionState["userId"] = input.userId
        sessionState["timestamp"] = System.currentTimeMillis()
        return InitOutput(sessionId = generateSessionId())
    }

    @Goal(name = "getSessionInfo", description = "Get current session information")
    fun getSessionInfo(input: SessionInput): SessionOutput {
        return SessionOutput(
            userId = sessionState["userId"] as? String,
            timestamp = sessionState["timestamp"] as? Long
        )
    }
}

McpAwareGoalTool Integration

Goal Tool Interface

McpAwareGoalTool: Interface for goals aware of MCP context { .api }

interface McpAwareGoalTool {
    fun executeMcp(input: Any, context: McpContext): Any
}

data class McpContext(
    val clientId: String,
    val requestId: String,
    val metadata: Map<String, String>
)

Implementation: { .api }

@Agent
class McpAwareAgent : McpAwareGoalTool {

    @Goal(
        name = "processWithContext",
        description = "Process data with MCP context awareness"
    )
    override fun executeMcp(input: Any, context: McpContext): Any {
        logger.info("Processing request ${context.requestId} from ${context.clientId}")

        val typedInput = input as ProcessInput

        // Use context for logging, metrics, etc.
        metricsService.recordRequest(context.clientId)

        val result = processData(typedInput)

        return ProcessOutput(
            result = result,
            requestId = context.requestId
        )
    }

    private fun processData(input: ProcessInput): String {
        // Business logic
        return "processed"
    }

    companion object {
        private val logger = LoggerFactory.getLogger(McpAwareAgent::class.java)
    }
}

Context Propagation

Publisher with Context: { .api }

@Service
class ContextAwarePublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback>
        get() = llmReferences.flatMap { reference ->
            McpToolExport.fromLlmReference(reference).toolCallbacks.map { callback ->
                ContextPropagatingToolCallback(callback)
            }
        }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "ContextAwarePublisher: ${toolCallbacks.size} tools"

    private class ContextPropagatingToolCallback(
        private val delegate: ToolCallback
    ) : ToolCallback by delegate {

        override fun call(functionArguments: String): String {
            val context = McpContext(
                clientId = extractClientId(),
                requestId = generateRequestId(),
                metadata = extractMetadata()
            )

            // If delegate is McpAwareGoalTool, pass context
            return if (delegate is McpAwareGoalTool) {
                val input = parseInput(functionArguments)
                val result = delegate.executeMcp(input, context)
                serializeOutput(result)
            } else {
                delegate.call(functionArguments)
            }
        }

        private fun extractClientId(): String = "client-${System.currentTimeMillis()}"
        private fun generateRequestId(): String = UUID.randomUUID().toString()
        private fun extractMetadata(): Map<String, String> = emptyMap()
        private fun parseInput(args: String): Any = TODO()
        private fun serializeOutput(result: Any): String = TODO()
    }
}

Resource Updates and Listeners

McpResourceUpdatingListener

Resource Change Notification: { .api }

interface McpResourceUpdatingListener {
    fun onResourceUpdated(resourceUri: String, newContent: String)
    fun onResourceDeleted(resourceUri: String)
}

@Service
class AgentResourceListener : McpResourceUpdatingListener {

    override fun onResourceUpdated(resourceUri: String, newContent: String) {
        logger.info("Resource updated: $resourceUri")
        // Notify relevant agents
        agentNotifier.notifyResourceChange(resourceUri, newContent)
    }

    override fun onResourceDeleted(resourceUri: String) {
        logger.info("Resource deleted: $resourceUri")
        // Clear agent caches
        agentCache.invalidate(resourceUri)
    }

    companion object {
        private val logger = LoggerFactory.getLogger(AgentResourceListener::class.java)
    }
}

Dynamic Resource Publishing: { .api }

@Service
class DynamicAgentResourcePublisher(
    private val llmReferences: List<LlmReference>,
    private val resourceListener: McpResourceUpdatingListener
) : McpAsyncResourcePublisher {

    private val resourceCache = ConcurrentHashMap<String, String>()

    override fun resources(): List<AsyncResourceSpecification> {
        return llmReferences.map { reference ->
            createAgentResource(reference)
        }
    }

    private fun createAgentResource(reference: LlmReference): AsyncResourceSpecification {
        val uri = "app://agents/${reference.simpleName}/info"

        return AsyncResourceSpecification(
            Resource(uri, reference.simpleName, reference.description, "application/json", null)
        ) { exchange, request ->
            Mono.fromCallable {
                val content = generateAgentInfo(reference)

                // Update cache
                val oldContent = resourceCache.put(uri, content)
                if (oldContent != content) {
                    resourceListener.onResourceUpdated(uri, content)
                }

                ReadResourceResult(
                    listOf(
                        TextResourceContents(uri, "application/json", content)
                    )
                )
            }
        }
    }

    private fun generateAgentInfo(reference: LlmReference): String {
        return objectMapper.writeValueAsString(
            mapOf(
                "name" to reference.name,
                "description" to reference.description,
                "goals" to reference.goals.map { goal ->
                    mapOf(
                        "name" to goal.name,
                        "description" to goal.description
                    )
                }
            )
        )
    }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "DynamicAgentResourcePublisher: ${resources().size} resources"

    companion object {
        private val objectMapper = ObjectMapper()
    }
}

Advanced Integration Patterns

Pattern 1: Agent Factory

Dynamic Agent Creation: { .api }

@Service
class AgentFactory {

    fun createAgent(type: AgentType, config: AgentConfig): LlmReference {
        return when (type) {
            AgentType.USER_MANAGEMENT -> createUserManagementAgent(config)
            AgentType.DATA_PROCESSING -> createDataProcessingAgent(config)
            AgentType.ANALYTICS -> createAnalyticsAgent(config)
        }
    }

    private fun createUserManagementAgent(config: AgentConfig): LlmReference {
        // Create and configure agent
        return TODO()
    }
}

@Service
class DynamicAgentPublisher(
    private val agentFactory: AgentFactory
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback>
        get() {
            val agents = listOf(
                agentFactory.createAgent(AgentType.USER_MANAGEMENT, defaultConfig),
                agentFactory.createAgent(AgentType.DATA_PROCESSING, defaultConfig)
            )

            return agents.flatMap { agent ->
                McpToolExport.fromLlmReference(agent).toolCallbacks
            }
        }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "DynamicAgentPublisher: ${toolCallbacks.size} tools"

    private val defaultConfig = AgentConfig()
}

Pattern 2: Agent Composition

Composite Agent Pattern: { .api }

@Agent
class CompositeAgent(
    private val userAgent: UserManagementAgent,
    private val dataAgent: DataProcessingAgent
) {

    @Goal(
        name = "createUserWithData",
        description = "Create user and initialize their data"
    )
    fun createUserWithData(input: CompositeInput): CompositeOutput {
        // Coordinate multiple agents
        val userResult = userAgent.createUser(
            CreateUserInput(input.email, input.name, input.role)
        )

        val dataResult = dataAgent.initializeData(
            InitDataInput(userResult.userId, input.initialData)
        )

        return CompositeOutput(
            userId = userResult.userId,
            dataInitialized = dataResult.success
        )
    }
}

Pattern 3: Agent Pipeline

Sequential Agent Execution: { .api }

@Service
class AgentPipeline(
    private val llmReferences: List<LlmReference>
) {

    fun executePipeline(stages: List<PipelineStage>, initialInput: Any): Any {
        var currentInput = initialInput

        for (stage in stages) {
            val agent = findAgent(stage.agentName)
            val goal = findGoal(agent, stage.goalName)

            currentInput = goal.invoke(currentInput)
        }

        return currentInput
    }

    private fun findAgent(name: String): LlmReference {
        return llmReferences.find { it.name == name }
            ?: throw IllegalArgumentException("Agent not found: $name")
    }

    private fun findGoal(agent: LlmReference, goalName: String): GoalDefinition {
        return agent.goals.find { it.name == goalName }
            ?: throw IllegalArgumentException("Goal not found: $goalName")
    }
}

data class PipelineStage(val agentName: String, val goalName: String)

Pattern 4: Agent Versioning

Version-Aware Agent Exposure: { .api }

@Service
class VersionedAgentPublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    private val versionedAgents = mapOf(
        "v1" to listOf("UserAgent", "DataAgent"),
        "v2" to listOf("UserAgentV2", "DataAgentV2", "AnalyticsAgent")
    )

    override val toolCallbacks: List<ToolCallback>
        get() = versionedAgents.flatMap { (version, agentNames) ->
            llmReferences
                .filter { it.simpleName in agentNames }
                .flatMap { reference ->
                    McpToolExport.fromLlmReference(
                        llmReference = reference,
                        namingStrategy = { goalName ->
                            "${version}_${reference.simpleName.lowercase()}_$goalName"
                        }
                    ).toolCallbacks
                }
        }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "VersionedAgentPublisher: ${toolCallbacks.size} tools across " +
        "${versionedAgents.size} versions"
}

Performance Considerations

Lazy Agent Initialization

Defer Expensive Operations: { .api }

@Service
class LazyAgentPublisher(
    private val llmReferences: List<LlmReference>
) : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback> by lazy {
        logger.info("Initializing agent tools...")
        val start = System.currentTimeMillis()

        val tools = llmReferences.flatMap { reference ->
            McpToolExport.fromLlmReference(reference).toolCallbacks
        }

        val duration = System.currentTimeMillis() - start
        logger.info("Initialized ${tools.size} tools in ${duration}ms")

        tools
    }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "LazyAgentPublisher: ${toolCallbacks.size} tools"

    companion object {
        private val logger = LoggerFactory.getLogger(LazyAgentPublisher::class.java)
    }
}

Agent Caching

Cache Agent Invocations: { .api }

@Service
class CachedAgentPublisher(
    private val llmReferences: List<LlmReference>,
    private val cacheManager: CacheManager
) : McpExportToolCallbackPublisher {

    private val cache = cacheManager.getCache("agent-results")

    override val toolCallbacks: List<ToolCallback>
        get() = llmReferences.flatMap { reference ->
            McpToolExport.fromLlmReference(reference).toolCallbacks.map { callback ->
                CachedToolCallback(callback, cache)
            }
        }

    override fun infoString(verbose: Boolean?, indent: Int): String =
        "CachedAgentPublisher: ${toolCallbacks.size} tools"

    private class CachedToolCallback(
        private val delegate: ToolCallback,
        private val cache: Cache?
    ) : ToolCallback by delegate {

        override fun call(functionArguments: String): String {
            val cacheKey = "${delegate.name}:$functionArguments"

            return cache?.get(cacheKey, String::class.java)
                ?: run {
                    val result = delegate.call(functionArguments)
                    cache?.put(cacheKey, result)
                    result
                }
        }
    }
}

Parallel Agent Execution

Execute Multiple Agents Concurrently: { .api }

@Service
class ParallelAgentExecutor(
    private val llmReferences: List<LlmReference>
) {

    fun executeParallel(requests: List<AgentRequest>): List<AgentResponse> {
        return requests.parallelStream()
            .map { request ->
                val agent = findAgent(request.agentName)
                val goal = findGoal(agent, request.goalName)
                val result = goal.invoke(request.input)

                AgentResponse(
                    agentName = request.agentName,
                    goalName = request.goalName,
                    result = result
                )
            }
            .toList()
    }

    private fun findAgent(name: String): LlmReference = TODO()
    private fun findGoal(agent: LlmReference, goalName: String): GoalDefinition = TODO()
}

data class AgentRequest(val agentName: String, val goalName: String, val input: Any)
data class AgentResponse(val agentName: String, val goalName: String, val result: Any)

Related Documentation

  • Architecture - Overall architecture
  • Custom Implementations - Extending the library
  • Performance - Performance optimization
  • Tool Export API - Tool conversion details
  • Integration Patterns Guide - Complete examples
tessl i tessl/maven-com-embabel-agent--embabel-agent-mcpserver@0.3.1

docs

advanced

architecture.md

custom-implementations.md

embabel-integration.md

error-handling.md

imports.md

performance.md

index.md

tile.json