Discover and Export available Agent(s) as MCP Servers
API reference for all publisher interfaces used to expose tools, resources, and prompts in the MCP server.
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.
com.embabel.agent.mcpserver // Tool publishers
com.embabel.agent.mcpserver.sync // Sync mode publishers
com.embabel.agent.mcpserver.async // Async mode publishersMode-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 registerMethods:
infoString(verbose: Boolean?, indent: Int): String - Human-readable description of publisherUsage:
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 */)
}
}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 specificationsinfoString(verbose: Boolean?, indent: Int): String - Human-readable descriptionUsage:
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)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 specificationsinfoString(verbose: Boolean?, indent: Int): String - Human-readable descriptionUsage:
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)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 specificationsinfoString(verbose: Boolean?, indent: Int): String - Human-readable descriptionUsage:
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
)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 specificationsinfoString(verbose: Boolean?, indent: Int): String - Human-readable descriptionUsage:
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
)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>
)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()
}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"
}// 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"
}The library includes built-in publishers automatically registered:
Provides helloBanner tool that displays server information.
// Automatically available in all servers
// Tool name: "helloBanner"
// Returns: Server name, version, and capabilitiesUsage from MCP client:
{
"tool": "helloBanner",
"arguments": {}
}Single Responsibility: Create focused publishers for specific concerns
@Service
class UserApiToolsPublisher : McpExportToolCallbackPublisher { /* ... */ }
@Service
class UserApiResourcesPublisher : McpResourcePublisher { /* ... */ }Lazy Evaluation: Use lazy for expensive operations
override val toolCallbacks: List<ToolCallback> by lazy {
computeExpensiveTools()
}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()
}
}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"
}
}Conditional Registration: Use Spring conditions for selective activation
@ConditionalOnProperty("features.enabled")
@Profile("production")
class ConditionalPublisher : McpExportToolCallbackPublisher { /* ... */ }