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

publishers.mddocs/api/

Publishers API

API reference for all publisher interfaces used to expose tools, resources, and prompts in the MCP server.

Overview

Publishers are Spring beans that provide tools, resources, and prompts to the MCP server. All publishers are auto-discovered via Spring component scanning and registered during server initialization.

Package

com.embabel.agent.mcpserver        // Tool publishers
com.embabel.agent.mcpserver.sync   // Sync mode publishers
com.embabel.agent.mcpserver.async  // Async mode publishers

Tool Publisher

McpExportToolCallbackPublisher { .api }

Mode-agnostic interface for publishing tools.

package com.embabel.agent.mcpserver

interface McpExportToolCallbackPublisher {
    val toolCallbacks: List<ToolCallback>
    fun infoString(verbose: Boolean?, indent: Int): String
}

Properties:

  • toolCallbacks: List<ToolCallback> - List of tool callbacks to register

Methods:

  • infoString(verbose: Boolean?, indent: Int): String - Human-readable description of publisher

Usage:

import com.embabel.agent.mcpserver.McpExportToolCallbackPublisher
import org.springframework.ai.tool.ToolCallback
import org.springframework.stereotype.Service

@Service
class CalculatorPublisher : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback>
        get() = listOf(
            createAddTool(),
            createSubtractTool(),
            createMultiplyTool()
        )

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

    private fun createAddTool(): ToolCallback = TODO()
    private fun createSubtractTool(): ToolCallback = TODO()
    private fun createMultiplyTool(): ToolCallback = TODO()
}

With McpToolExport:

import com.embabel.agent.mcpserver.McpToolExport
import com.embabel.agent.api.common.ToolObject

@Service
class ApiToolPublisher : McpExportToolCallbackPublisher {

    override val toolCallbacks: List<ToolCallback>
        get() {
            val toolObject = ToolObject(
                objects = listOf(getUserTool(), createUserTool()),
                namingStrategy = { "api_$it" }
            )
            return McpToolExport.fromToolObject(toolObject).toolCallbacks
        }

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

    private fun getUserTool(): Any = TODO()
    private fun createUserTool(): Any = TODO()
}

Lazy Evaluation:

@Service
class ExpensivePublisher : McpExportToolCallbackPublisher {

    // Computed only once when first accessed
    override val toolCallbacks: List<ToolCallback> by lazy {
        loadToolsFromDatabase()
    }

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

    private fun loadToolsFromDatabase(): List<ToolCallback> {
        // Expensive operation
        return listOf(/* tools */)
    }
}

Resource Publishers (Sync Mode)

McpResourcePublisher { .api }

Interface for publishing resources in synchronous mode.

package com.embabel.agent.mcpserver.sync

import io.modelcontextprotocol.server.McpServerFeatures

interface McpResourcePublisher {
    fun resources(): List<McpServerFeatures.SyncResourceSpecification>
    fun infoString(verbose: Boolean?, indent: Int): String
}

Methods:

  • resources(): List<SyncResourceSpecification> - List of resource specifications
  • infoString(verbose: Boolean?, indent: Int): String - Human-readable description

Usage:

import com.embabel.agent.mcpserver.sync.McpResourcePublisher
import com.embabel.agent.mcpserver.sync.SyncResourceSpecificationFactory
import io.modelcontextprotocol.server.McpServerFeatures
import org.springframework.stereotype.Service

@Service
class DocumentationPublisher : McpResourcePublisher {

    override fun resources(): List<McpServerFeatures.SyncResourceSpecification> {
        return listOf(
            SyncResourceSpecificationFactory.staticSyncResourceSpecification(
                uri = "app://docs/api-reference",
                name = "APIReference",
                description = "API reference documentation",
                content = loadApiDocs(),
                mimeType = "text/markdown"
            ),
            SyncResourceSpecificationFactory.staticSyncResourceSpecification(
                uri = "app://docs/getting-started",
                name = "GettingStarted",
                description = "Getting started guide",
                content = loadGettingStarted(),
                mimeType = "text/markdown"
            )
        )
    }

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

    private fun loadApiDocs(): String = "# API Reference\n..."
    private fun loadGettingStarted(): String = "# Getting Started\n..."
}

Dynamic Resources:

import io.modelcontextprotocol.server.McpSyncServerExchange

@Service
class StatusPublisher : McpResourcePublisher {

    override fun resources(): List<McpServerFeatures.SyncResourceSpecification> {
        return listOf(
            SyncResourceSpecificationFactory.syncResourceSpecification(
                uri = "app://status/current",
                name = "CurrentStatus",
                description = "Real-time system status",
                resourceLoader = { exchange: McpSyncServerExchange ->
                    val status = getCurrentStatus()
                    objectMapper.writeValueAsString(status)
                },
                mimeType = "application/json"
            )
        )
    }

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

    private fun getCurrentStatus(): Status = Status(healthy = true, uptime = 1000)

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

data class Status(val healthy: Boolean, val uptime: Long)

Resource Publishers (Async Mode)

McpAsyncResourcePublisher { .api }

Interface for publishing resources in asynchronous mode.

package com.embabel.agent.mcpserver.async

import io.modelcontextprotocol.server.McpServerFeatures

interface McpAsyncResourcePublisher {
    fun resources(): List<McpServerFeatures.AsyncResourceSpecification>
    fun infoString(verbose: Boolean?, indent: Int): String
}

Methods:

  • resources(): List<AsyncResourceSpecification> - List of async resource specifications
  • infoString(verbose: Boolean?, indent: Int): String - Human-readable description

Usage:

import com.embabel.agent.mcpserver.async.McpAsyncResourcePublisher
import io.modelcontextprotocol.server.McpServerFeatures
import io.modelcontextprotocol.spec.McpSchema
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono
import reactor.core.scheduler.Schedulers

@Service
@ConditionalOnProperty(
    value = ["spring.ai.mcp.server.type"],
    havingValue = "ASYNC"
)
class AsyncDataPublisher : McpAsyncResourcePublisher {

    override fun resources(): List<McpServerFeatures.AsyncResourceSpecification> {
        return listOf(
            McpServerFeatures.AsyncResourceSpecification(
                McpSchema.Resource(
                    "app://data/users",
                    "Users",
                    "User data from database",
                    "application/json",
                    null
                )
            ) { exchange, request ->
                loadUsersAsync()
                    .map { users ->
                        McpSchema.ReadResourceResult(
                            listOf(
                                McpSchema.TextResourceContents(
                                    "app://data/users",
                                    "application/json",
                                    objectMapper.writeValueAsString(users)
                                )
                            )
                        )
                    }
            }
        )
    }

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

    private fun loadUsersAsync(): Mono<List<User>> {
        return Mono.fromCallable {
            listOf(User("1", "Alice"), User("2", "Bob"))
        }.subscribeOn(Schedulers.boundedElastic())
    }

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

data class User(val id: String, val name: String)

Prompt Publishers (Sync Mode)

McpPromptPublisher { .api }

Interface for publishing prompts in synchronous mode.

package com.embabel.agent.mcpserver.sync

import io.modelcontextprotocol.server.McpServerFeatures

interface McpPromptPublisher {
    fun prompts(): List<McpServerFeatures.SyncPromptSpecification>
    fun infoString(verbose: Boolean?, indent: Int): String
}

Methods:

  • prompts(): List<SyncPromptSpecification> - List of prompt specifications
  • infoString(verbose: Boolean?, indent: Int): String - Human-readable description

Usage:

import com.embabel.agent.mcpserver.sync.McpPromptPublisher
import com.embabel.agent.mcpserver.sync.McpPromptFactory
import com.embabel.common.core.types.Named
import com.embabel.common.core.types.Described
import com.fasterxml.jackson.annotation.JsonPropertyDescription
import io.modelcontextprotocol.server.McpServerFeatures
import org.springframework.stereotype.Service

@Service
class TaskPromptsPublisher : McpPromptPublisher {

    private val factory = McpPromptFactory()

    override fun prompts(): List<McpServerFeatures.SyncPromptSpecification> {
        return listOf(
            createTaskPrompt(),
            searchTasksPrompt()
        )
    }

    override fun infoString(verbose: Boolean?, indent: Int): String {
        return "TaskPromptsPublisher: ${prompts().size} prompts"
    }

    private fun createTaskPrompt(): McpServerFeatures.SyncPromptSpecification {
        val goal = object : Named, Described {
            override val name = "createTask"
            override val description = "Create a new task"
        }

        return factory.syncPromptSpecificationForType(
            goal = goal,
            inputType = CreateTaskInput::class.java
        )
    }

    private fun searchTasksPrompt(): McpServerFeatures.SyncPromptSpecification {
        val goal = object : Named, Described {
            override val name = "searchTasks"
            override val description = "Search for tasks"
        }

        return factory.syncPromptSpecificationForType(
            goal = goal,
            inputType = SearchTasksInput::class.java
        )
    }
}

data class CreateTaskInput(
    @JsonPropertyDescription("Task title")
    val title: String,

    @JsonPropertyDescription("Task description")
    val description: String,

    @JsonPropertyDescription("Priority level (1-5)")
    val priority: Int
)

data class SearchTasksInput(
    @JsonPropertyDescription("Search query")
    val query: String,

    @JsonPropertyDescription("Filter by status (optional)")
    val status: String? = null
)

Prompt Publishers (Async Mode)

McpAsyncPromptPublisher { .api }

Interface for publishing prompts in asynchronous mode.

package com.embabel.agent.mcpserver.async

import io.modelcontextprotocol.server.McpServerFeatures

interface McpAsyncPromptPublisher {
    fun prompts(): List<McpServerFeatures.AsyncPromptSpecification>
    fun infoString(verbose: Boolean?, indent: Int): String
}

Methods:

  • prompts(): List<AsyncPromptSpecification> - List of async prompt specifications
  • infoString(verbose: Boolean?, indent: Int): String - Human-readable description

Usage:

import com.embabel.agent.mcpserver.async.McpAsyncPromptPublisher
import com.embabel.agent.mcpserver.async.McpAsyncPromptFactory
import com.embabel.common.core.types.Named
import com.embabel.common.core.types.Described
import com.fasterxml.jackson.annotation.JsonPropertyDescription
import io.modelcontextprotocol.server.McpServerFeatures
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.stereotype.Service

@Service
@ConditionalOnProperty(
    value = ["spring.ai.mcp.server.type"],
    havingValue = "ASYNC"
)
class AsyncAnalysisPromptsPublisher : McpAsyncPromptPublisher {

    private val factory = McpAsyncPromptFactory()

    override fun prompts(): List<McpServerFeatures.AsyncPromptSpecification> {
        val goal = object : Named, Described {
            override val name = "analyzeData"
            override val description = "Analyze data asynchronously"
        }

        return listOf(
            factory.asyncPromptSpecificationForType(
                goal = goal,
                inputType = AnalysisInput::class.java
            )
        )
    }

    override fun infoString(verbose: Boolean?, indent: Int): String {
        return "AsyncAnalysisPromptsPublisher: ${prompts().size} prompts"
    }
}

data class AnalysisInput(
    @JsonPropertyDescription("Data source identifier")
    val dataSource: String,

    @JsonPropertyDescription("Type of analysis to perform")
    val analysisType: String,

    @JsonPropertyDescription("Time range (optional)")
    val timeRange: String? = null
)

Combined Publishers

Multiple Interfaces { .api }

A single class can implement multiple publisher interfaces:

@Service
class UnifiedPublisher :
    McpExportToolCallbackPublisher,
    McpResourcePublisher,
    McpPromptPublisher {

    private val promptFactory = McpPromptFactory()

    // Tools
    override val toolCallbacks: List<ToolCallback>
        get() = McpToolExport.fromToolObject(
            ToolObject(
                objects = listOf(processTool, analyzeTool),
                namingStrategy = { "unified_$it" }
            )
        ).toolCallbacks

    // Resources
    override fun resources(): List<McpServerFeatures.SyncResourceSpecification> {
        return listOf(
            SyncResourceSpecificationFactory.staticSyncResourceSpecification(
                uri = "app://unified/help",
                name = "Help",
                description = "Help documentation",
                content = "# Help\n\nUsage instructions...",
                mimeType = "text/markdown"
            )
        )
    }

    // Prompts
    override fun prompts(): List<McpServerFeatures.SyncPromptSpecification> {
        val goal = object : Named, Described {
            override val name = "unifiedAction"
            override val description = "Execute unified action"
        }

        return listOf(
            promptFactory.syncPromptSpecificationForType(
                goal = goal,
                inputType = UnifiedInput::class.java
            )
        )
    }

    override fun infoString(verbose: Boolean?, indent: Int): String {
        return "UnifiedPublisher: ${toolCallbacks.size} tools, " +
               "${resources().size} resources, ${prompts().size} prompts"
    }

    private val processTool: Any = TODO()
    private val analyzeTool: Any = TODO()
}

data class UnifiedInput(
    val action: String,
    val parameters: Map<String, Any>
)

Conditional Publishers

Profile-Based { .api }

import org.springframework.context.annotation.Profile

@Service
@Profile("development")
class DevelopmentToolsPublisher : McpExportToolCallbackPublisher {
    override val toolCallbacks = listOf(
        createDebugTool(),
        createProfilingTool()
    )

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

    private fun createDebugTool(): ToolCallback = TODO()
    private fun createProfilingTool(): ToolCallback = TODO()
}

@Service
@Profile("production")
class ProductionToolsPublisher : McpExportToolCallbackPublisher {
    override val toolCallbacks = listOf(
        createHealthCheckTool(),
        createMetricsTool()
    )

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

    private fun createHealthCheckTool(): ToolCallback = TODO()
    private fun createMetricsTool(): ToolCallback = TODO()
}

Property-Based { .api }

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty

@Service
@ConditionalOnProperty(
    name = ["features.experimental"],
    havingValue = "true"
)
class ExperimentalPublisher : McpExportToolCallbackPublisher {
    override val toolCallbacks = listOf(/* experimental tools */)
    override fun infoString(verbose: Boolean?, indent: Int) = "ExperimentalPublisher"
}

@Service
@ConditionalOnProperty(
    name = ["features.analytics"],
    havingValue = "true"
)
class AnalyticsPublisher : McpResourcePublisher {
    override fun resources() = listOf(/* analytics resources */)
    override fun infoString(verbose: Boolean?, indent: Int) = "AnalyticsPublisher"
}

Mode-Specific { .api }

// Sync mode only
@Service
@ConditionalOnProperty(
    value = ["spring.ai.mcp.server.type"],
    havingValue = "SYNC",
    matchIfMissing = true
)
class SyncResourcesPublisher : McpResourcePublisher {
    override fun resources() = listOf(/* sync resources */)
    override fun infoString(verbose: Boolean?, indent: Int) = "SyncResourcesPublisher"
}

// Async mode only
@Service
@ConditionalOnProperty(
    value = ["spring.ai.mcp.server.type"],
    havingValue = "ASYNC"
)
class AsyncResourcesPublisher : McpAsyncResourcePublisher {
    override fun resources() = listOf(/* async resources */)
    override fun infoString(verbose: Boolean?, indent: Int) = "AsyncResourcesPublisher"
}

Built-in Publishers

The library includes built-in publishers automatically registered:

HelloBannerPublisher { .api }

Provides helloBanner tool that displays server information.

// Automatically available in all servers
// Tool name: "helloBanner"
// Returns: Server name, version, and capabilities

Usage from MCP client:

{
  "tool": "helloBanner",
  "arguments": {}
}

Best Practices

  1. Single Responsibility: Create focused publishers for specific concerns

    @Service
    class UserApiToolsPublisher : McpExportToolCallbackPublisher { /* ... */ }
    
    @Service
    class UserApiResourcesPublisher : McpResourcePublisher { /* ... */ }
  2. Lazy Evaluation: Use lazy for expensive operations

    override val toolCallbacks: List<ToolCallback> by lazy {
        computeExpensiveTools()
    }
  3. Error Handling: Return empty lists rather than throwing

    override fun resources(): List<McpServerFeatures.SyncResourceSpecification> {
        return try {
            loadResources()
        } catch (e: Exception) {
            logger.error("Failed to load resources", e)
            emptyList()
        }
    }
  4. Meaningful Info Strings: Provide descriptive information

    override fun infoString(verbose: Boolean?, indent: Int): String {
        return if (verbose == true) {
            "ApiPublisher: ${toolCallbacks.size} tools (auth: 3, data: 5)"
        } else {
            "ApiPublisher: ${toolCallbacks.size} tools"
        }
    }
  5. Conditional Registration: Use Spring conditions for selective activation

    @ConditionalOnProperty("features.enabled")
    @Profile("production")
    class ConditionalPublisher : McpExportToolCallbackPublisher { /* ... */ }

See Also

  • Creating Publishers Guide - Publisher implementation patterns
  • Tool Export API - McpToolExport factory methods
  • Factories API - Resource and prompt factories
  • Execution Modes Guide - Sync vs async mode selection
tessl i tessl/maven-com-embabel-agent--embabel-agent-mcpserver@0.3.1

docs

index.md

tile.json