Discover and Export available Agent(s) as MCP Servers
API reference for resource and prompt specification factories that simplify creation of MCP resources and prompts.
com.embabel.agent.mcpserver.sync // Sync mode factories
com.embabel.agent.mcpserver.async // Async mode factoriesFactories provide convenient methods for creating resource and prompt specifications with type-safe APIs. They handle boilerplate code and ensure correct MCP protocol structures.
Factory for creating synchronous resource specifications.
package com.embabel.agent.mcpserver.sync
import io.modelcontextprotocol.server.McpServerFeatures
import io.modelcontextprotocol.server.McpSyncServerExchange
object SyncResourceSpecificationFactory {
fun staticSyncResourceSpecification(
uri: String,
name: String,
description: String,
content: String,
mimeType: String
): McpServerFeatures.SyncResourceSpecification
fun syncResourceSpecification(
uri: String,
name: String,
description: String,
resourceLoader: (McpSyncServerExchange) -> String,
mimeType: String
): McpServerFeatures.SyncResourceSpecification
}Create a static resource with fixed content.
fun staticSyncResourceSpecification(
uri: String,
name: String,
description: String,
content: String,
mimeType: String
): McpServerFeatures.SyncResourceSpecificationParameters:
uri: String - Resource URI (e.g., "app://docs/readme")name: String - Human-readable namedescription: String - Resource descriptioncontent: String - Static contentmimeType: String - MIME type (e.g., "text/markdown", "application/json")Returns: SyncResourceSpecification ready for registration
Usage:
import com.embabel.agent.mcpserver.sync.SyncResourceSpecificationFactory
import com.embabel.agent.mcpserver.sync.McpResourcePublisher
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 = "Complete API reference documentation",
content = loadApiDocs(),
mimeType = "text/markdown"
),
SyncResourceSpecificationFactory.staticSyncResourceSpecification(
uri = "app://data/schema",
name = "DataSchema",
description = "JSON schema for data structures",
content = loadSchema(),
mimeType = "application/json"
)
)
}
override fun infoString(verbose: Boolean?, indent: Int) =
"DocumentationPublisher: ${resources().size} resources"
private fun loadApiDocs(): String = """
# API Reference
## Authentication
- POST /api/auth/login
- POST /api/auth/logout
""".trimIndent()
private fun loadSchema(): String = """
{
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"}
}
}
""".trimIndent()
}Common MIME Types:
// Markdown documentation
mimeType = "text/markdown"
// JSON data
mimeType = "application/json"
// Plain text
mimeType = "text/plain"
// HTML content
mimeType = "text/html"
// XML data
mimeType = "application/xml"Create a dynamic resource with on-demand content loading.
fun syncResourceSpecification(
uri: String,
name: String,
description: String,
resourceLoader: (McpSyncServerExchange) -> String,
mimeType: String
): McpServerFeatures.SyncResourceSpecificationParameters:
uri: String - Resource URIname: String - Human-readable namedescription: String - Resource descriptionresourceLoader: (McpSyncServerExchange) -> String - Lambda that loads contentmimeType: String - MIME typeReturns: SyncResourceSpecification with dynamic loading
Usage:
import io.modelcontextprotocol.server.McpSyncServerExchange
import com.fasterxml.jackson.databind.ObjectMapper
@Service
class StatusPublisher : McpResourcePublisher {
private val objectMapper = ObjectMapper()
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"
),
SyncResourceSpecificationFactory.syncResourceSpecification(
uri = "app://metrics/performance",
name = "Performance",
description = "Performance metrics",
resourceLoader = { exchange ->
val metrics = collectMetrics()
objectMapper.writeValueAsString(metrics)
},
mimeType = "application/json"
)
)
}
override fun infoString(verbose: Boolean?, indent: Int) =
"StatusPublisher: ${resources().size} resources"
private fun getCurrentStatus(): Status {
return Status(
healthy = true,
uptime = System.currentTimeMillis(),
activeUsers = getActiveUserCount()
)
}
private fun collectMetrics(): Metrics {
return Metrics(
cpu = getCpuUsage(),
memory = getMemoryUsage(),
requests = getRequestCount()
)
}
private fun getActiveUserCount(): Int = 42
private fun getCpuUsage(): Double = 45.5
private fun getMemoryUsage(): Double = 67.8
private fun getRequestCount(): Long = 1000
}
data class Status(val healthy: Boolean, val uptime: Long, val activeUsers: Int)
data class Metrics(val cpu: Double, val memory: Double, val requests: Long)Exchange Usage:
SyncResourceSpecificationFactory.syncResourceSpecification(
uri = "app://user/preferences",
name = "UserPreferences",
description = "User-specific preferences",
resourceLoader = { exchange ->
// Access request context from exchange if needed
val userId = extractUserIdFromExchange(exchange)
val prefs = loadUserPreferences(userId)
objectMapper.writeValueAsString(prefs)
},
mimeType = "application/json"
)Factory for creating synchronous prompt specifications from Java/Kotlin types.
package com.embabel.agent.mcpserver.sync
import io.modelcontextprotocol.server.McpServerFeatures
import com.embabel.common.core.types.Named
import com.embabel.common.core.types.Described
class McpPromptFactory {
fun syncPromptSpecificationForType(
goal: Named & Described,
inputType: Class<*>
): McpServerFeatures.SyncPromptSpecification
}Create prompt specification from input type class.
fun syncPromptSpecificationForType(
goal: Named & Described,
inputType: Class<*>
): McpServerFeatures.SyncPromptSpecificationParameters:
goal: Named & Described - Goal object with name and descriptioninputType: Class<*> - Input type class (generates JSON schema)Returns: SyncPromptSpecification for the goal
Usage:
import com.embabel.agent.mcpserver.sync.McpPromptFactory
import com.embabel.agent.mcpserver.sync.McpPromptPublisher
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(),
updateTaskPrompt(),
searchTasksPrompt()
)
}
override fun infoString(verbose: Boolean?, indent: Int) =
"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 in the system"
}
return factory.syncPromptSpecificationForType(
goal = goal,
inputType = CreateTaskInput::class.java
)
}
private fun updateTaskPrompt(): McpServerFeatures.SyncPromptSpecification {
val goal = object : Named, Described {
override val name = "updateTask"
override val description = "Update an existing task"
}
return factory.syncPromptSpecificationForType(
goal = goal,
inputType = UpdateTaskInput::class.java
)
}
private fun searchTasksPrompt(): McpServerFeatures.SyncPromptSpecification {
val goal = object : Named, Described {
override val name = "searchTasks"
override val description = "Search for tasks matching criteria"
}
return factory.syncPromptSpecificationForType(
goal = goal,
inputType = SearchTasksInput::class.java
)
}
}
data class CreateTaskInput(
@JsonPropertyDescription("Title of the task (max 100 characters)")
val title: String,
@JsonPropertyDescription("Detailed description of the task")
val description: String,
@JsonPropertyDescription("Priority level (1-5, where 5 is highest)")
val priority: Int,
@JsonPropertyDescription("Due date in ISO 8601 format (optional)")
val dueDate: String? = null
)
data class UpdateTaskInput(
@JsonPropertyDescription("ID of task to update")
val taskId: String,
@JsonPropertyDescription("New title (optional)")
val title: String? = null,
@JsonPropertyDescription("New description (optional)")
val description: String? = null,
@JsonPropertyDescription("New priority (optional)")
val priority: Int? = null
)
data class SearchTasksInput(
@JsonPropertyDescription("Search query string")
val query: String,
@JsonPropertyDescription("Filter by status (optional)")
val status: String? = null,
@JsonPropertyDescription("Filter by priority (optional)")
val priority: Int? = null
)Input Type Design:
// Simple input
data class SimpleInput(
@JsonPropertyDescription("The operation to perform")
val operation: String
)
// Nested types
data class ComplexInput(
@JsonPropertyDescription("User information")
val user: UserInfo,
@JsonPropertyDescription("Processing options")
val options: ProcessingOptions
)
data class UserInfo(
@JsonPropertyDescription("User ID")
val id: String,
@JsonPropertyDescription("User name")
val name: String
)
data class ProcessingOptions(
@JsonPropertyDescription("Enable verbose output")
val verbose: Boolean = false,
@JsonPropertyDescription("Maximum items to process")
val limit: Int = 100
)
// Collections
data class BatchInput(
@JsonPropertyDescription("List of item IDs")
val items: List<String>,
@JsonPropertyDescription("Configuration parameters")
val config: Map<String, String> = emptyMap()
)Factory for creating asynchronous prompt specifications.
package com.embabel.agent.mcpserver.async
import io.modelcontextprotocol.server.McpServerFeatures
import com.embabel.common.core.types.Named
import com.embabel.common.core.types.Described
class McpAsyncPromptFactory {
fun asyncPromptSpecificationForType(
goal: Named & Described,
inputType: Class<*>
): McpServerFeatures.AsyncPromptSpecification
}Create async prompt specification from input type class.
fun asyncPromptSpecificationForType(
goal: Named & Described,
inputType: Class<*>
): McpServerFeatures.AsyncPromptSpecificationParameters:
goal: Named & Described - Goal object with name and descriptioninputType: Class<*> - Input type classReturns: AsyncPromptSpecification for the goal
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> {
return listOf(
analyzeDataPrompt(),
generateReportPrompt()
)
}
override fun infoString(verbose: Boolean?, indent: Int) =
"AsyncAnalysisPromptsPublisher: ${prompts().size} prompts"
private fun analyzeDataPrompt(): McpServerFeatures.AsyncPromptSpecification {
val goal = object : Named, Described {
override val name = "analyzeData"
override val description = "Perform asynchronous data analysis"
}
return factory.asyncPromptSpecificationForType(
goal = goal,
inputType = AnalysisInput::class.java
)
}
private fun generateReportPrompt(): McpServerFeatures.AsyncPromptSpecification {
val goal = object : Named, Described {
override val name = "generateReport"
override val description = "Generate comprehensive report"
}
return factory.asyncPromptSpecificationForType(
goal = goal,
inputType = ReportInput::class.java
)
}
}
data class AnalysisInput(
@JsonPropertyDescription("Data source identifier")
val dataSource: String,
@JsonPropertyDescription("Type of analysis to perform")
val analysisType: String,
@JsonPropertyDescription("Time range for analysis")
val timeRange: String? = null
)
data class ReportInput(
@JsonPropertyDescription("Report type")
val reportType: String,
@JsonPropertyDescription("Output format (pdf, excel, html)")
val format: String = "pdf",
@JsonPropertyDescription("Include charts")
val includeCharts: Boolean = true
)@Service
class SystemResourcesPublisher : McpResourcePublisher {
override fun resources(): List<McpServerFeatures.SyncResourceSpecification> {
return listOf(
createHealthResource(),
createMetricsResource(),
createConfigResource()
)
}
override fun infoString(verbose: Boolean?, indent: Int) =
"SystemResourcesPublisher: ${resources().size} resources"
private fun createHealthResource() =
SyncResourceSpecificationFactory.staticSyncResourceSpecification(
uri = "app://system/health",
name = "Health",
description = "System health status",
content = """{"status": "healthy", "uptime": 1000}""",
mimeType = "application/json"
)
private fun createMetricsResource() =
SyncResourceSpecificationFactory.syncResourceSpecification(
uri = "app://system/metrics",
name = "Metrics",
description = "Current system metrics",
resourceLoader = { getMetrics() },
mimeType = "application/json"
)
private fun createConfigResource() =
SyncResourceSpecificationFactory.staticSyncResourceSpecification(
uri = "app://system/config",
name = "Configuration",
description = "System configuration",
content = loadConfiguration(),
mimeType = "application/json"
)
private fun getMetrics(): String = """{"cpu": 45, "memory": 67}"""
private fun loadConfiguration(): String = """{"debug": false, "timeout": 30}"""
}@Service
class WorkflowPromptsPublisher : McpPromptPublisher {
private val factory = McpPromptFactory()
override fun prompts(): List<McpServerFeatures.SyncPromptSpecification> {
return listOf(
createWorkflowPrompt(),
updateWorkflowPrompt(),
executeWorkflowPrompt()
)
}
override fun infoString(verbose: Boolean?, indent: Int) =
"WorkflowPromptsPublisher: ${prompts().size} prompts"
private fun createWorkflowPrompt() = factory.syncPromptSpecificationForType(
goal = namedGoal("createWorkflow", "Create a new workflow"),
inputType = CreateWorkflowInput::class.java
)
private fun updateWorkflowPrompt() = factory.syncPromptSpecificationForType(
goal = namedGoal("updateWorkflow", "Update existing workflow"),
inputType = UpdateWorkflowInput::class.java
)
private fun executeWorkflowPrompt() = factory.syncPromptSpecificationForType(
goal = namedGoal("executeWorkflow", "Execute a workflow"),
inputType = ExecuteWorkflowInput::class.java
)
private fun namedGoal(name: String, description: String) = object : Named, Described {
override val name = name
override val description = description
}
}
data class CreateWorkflowInput(
@JsonPropertyDescription("Workflow name")
val name: String,
@JsonPropertyDescription("Workflow steps")
val steps: List<String>
)
data class UpdateWorkflowInput(
@JsonPropertyDescription("Workflow ID")
val workflowId: String,
@JsonPropertyDescription("Updated steps")
val steps: List<String>
)
data class ExecuteWorkflowInput(
@JsonPropertyDescription("Workflow ID")
val workflowId: String,
@JsonPropertyDescription("Input parameters")
val parameters: Map<String, Any>
)Use Static Resources for Fixed Content
// Good: Static content loaded once
staticSyncResourceSpecification(
uri = "app://docs/readme",
name = "README",
description = "Project documentation",
content = readmeContent,
mimeType = "text/markdown"
)Use Dynamic Resources for Real-Time Data
// Good: Content loaded on each request
syncResourceSpecification(
uri = "app://status/current",
name = "Status",
description = "Current status",
resourceLoader = { getCurrentStatus() },
mimeType = "application/json"
)Document All Input Fields
// Good: Clear descriptions
data class Input(
@JsonPropertyDescription("User ID (required)")
val userId: String,
@JsonPropertyDescription("Optional comment")
val comment: String? = null
)Use Appropriate MIME Types
// Match content type
mimeType = "text/markdown" // For Markdown
mimeType = "application/json" // For JSON
mimeType = "text/plain" // For plain textHandle Errors Gracefully
resourceLoader = { exchange ->
try {
loadData()
} catch (e: Exception) {
logger.error("Failed to load resource", e)
"""{"error": "Data unavailable"}"""
}
}