Move features(tools, prompts, resources) management to the FeatureRegistry (#365)
Move features(tools, prompts, resources) management to the `FeatureRegistry` to unload `Server` As for 'feature' naming, I refer to https://modelcontextprotocol.io/docs/learn/server-concepts
This commit is contained in:
parent
ee341df11e
commit
913c0d111e
5 changed files with 245 additions and 177 deletions
|
|
@ -5,33 +5,35 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt {
|
|||
public static final fun mcp (Lio/ktor/server/routing/Routing;Lkotlin/jvm/functions/Function1;)V
|
||||
}
|
||||
|
||||
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt {
|
||||
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt : io/modelcontextprotocol/kotlin/sdk/server/Feature {
|
||||
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)V
|
||||
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Prompt;
|
||||
public final fun component2 ()Lkotlin/jvm/functions/Function2;
|
||||
public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt;
|
||||
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt;Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public fun getKey ()Ljava/lang/String;
|
||||
public final fun getMessageProvider ()Lkotlin/jvm/functions/Function2;
|
||||
public final fun getPrompt ()Lio/modelcontextprotocol/kotlin/sdk/Prompt;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredResource {
|
||||
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredResource : io/modelcontextprotocol/kotlin/sdk/server/Feature {
|
||||
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;)V
|
||||
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Resource;
|
||||
public final fun component2 ()Lkotlin/jvm/functions/Function2;
|
||||
public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource;
|
||||
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource;Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public fun getKey ()Ljava/lang/String;
|
||||
public final fun getReadHandler ()Lkotlin/jvm/functions/Function2;
|
||||
public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/Resource;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool {
|
||||
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool : io/modelcontextprotocol/kotlin/sdk/server/Feature {
|
||||
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)V
|
||||
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Tool;
|
||||
public final fun component2 ()Lkotlin/jvm/functions/Function2;
|
||||
|
|
@ -39,6 +41,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool {
|
|||
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool;Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool;
|
||||
public fun equals (Ljava/lang/Object;)Z
|
||||
public final fun getHandler ()Lkotlin/jvm/functions/Function2;
|
||||
public fun getKey ()Ljava/lang/String;
|
||||
public final fun getTool ()Lio/modelcontextprotocol/kotlin/sdk/Tool;
|
||||
public fun hashCode ()I
|
||||
public fun toString ()Ljava/lang/String;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
package io.modelcontextprotocol.kotlin.sdk.server
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.atomicfu.getAndUpdate
|
||||
import kotlinx.atomicfu.update
|
||||
import kotlinx.collections.immutable.minus
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toPersistentSet
|
||||
|
||||
/**
|
||||
* A generic registry for managing features of a specified type. This class provides thread-safe
|
||||
* operations for adding, removing, and retrieving features from the registry.
|
||||
*
|
||||
* @param T The type of the feature, constrained to implement the [Feature] interface.
|
||||
* @param featureType A string description of the type of feature being managed.
|
||||
* Used primarily for logging purposes.
|
||||
*/
|
||||
internal class FeatureRegistry<T : Feature>(private val featureType: String) {
|
||||
|
||||
private val logger = KotlinLogging.logger(name = "FeatureRegistry[$featureType]")
|
||||
|
||||
/**
|
||||
* Atomic variable used to maintain a thread-safe registry of features.
|
||||
* Stores a persistent map where each feature is identified by a unique key.
|
||||
*/
|
||||
private val registry = atomic(persistentMapOf<FeatureKey, T>())
|
||||
|
||||
/**
|
||||
* Provides a snapshot of all features currently registered in the registry.
|
||||
* Keys represent unique identifiers for each feature, and values represent the associated features.
|
||||
*/
|
||||
internal val values: Map<FeatureKey, T>
|
||||
get() = registry.value
|
||||
|
||||
/**
|
||||
* Adds the specified feature to the registry.
|
||||
*
|
||||
* @param feature The feature to be added to the registry.
|
||||
*/
|
||||
internal fun add(feature: T) {
|
||||
logger.info { "Adding $featureType: \"${feature.key}\"" }
|
||||
registry.update { current -> current.put(feature.key, feature) }
|
||||
logger.info { "Added $featureType: \"${feature.key}\"" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given list of features to the registry. Each feature is mapped by its key
|
||||
* and added to the current registry.
|
||||
*
|
||||
* @param features The list of features to add to the registry.
|
||||
*/
|
||||
internal fun addAll(features: List<T>) {
|
||||
logger.info { "Adding ${featureType}s: ${features.size}" }
|
||||
registry.update { current -> current.putAll(features.associateBy { it.key }) }
|
||||
logger.info { "Added ${featureType}s: ${features.size}" }
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the feature associated with the given key from the registry.
|
||||
*
|
||||
* @param key The key of the feature to be removed.
|
||||
* @return `true` if the feature was successfully removed, or `false` if the feature was not found.
|
||||
*/
|
||||
internal fun remove(key: FeatureKey): Boolean {
|
||||
logger.info { "Removing $featureType: \"$key\"" }
|
||||
val oldMap = registry.getAndUpdate { current -> current.remove(key) }
|
||||
|
||||
val removed = key in oldMap
|
||||
logger.info {
|
||||
if (removed) {
|
||||
"Removed $featureType: \"$key\""
|
||||
} else {
|
||||
"$featureType not found: \"$key\""
|
||||
}
|
||||
}
|
||||
return key in oldMap
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the features associated with the given keys from the registry.
|
||||
*
|
||||
* @param keys The list of keys whose associated features are to be removed.
|
||||
* @return The number of features that were successfully removed.
|
||||
*/
|
||||
internal fun removeAll(keys: List<FeatureKey>): Int {
|
||||
logger.info { "Removing ${featureType}s: ${keys.size}" }
|
||||
val oldMap = registry.getAndUpdate { current -> current - keys.toPersistentSet() }
|
||||
|
||||
val removedCount = keys.count { it in oldMap }
|
||||
logger.info {
|
||||
if (removedCount > 0) {
|
||||
"Removed ${featureType}s: $removedCount"
|
||||
} else {
|
||||
"No $featureType were removed"
|
||||
}
|
||||
}
|
||||
|
||||
return removedCount
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the feature associated with the given key from the registry.
|
||||
*
|
||||
* @param key The key of the feature to retrieve.
|
||||
* @return The feature associated with the given key, or `null` if no such feature exists in the registry.
|
||||
*/
|
||||
internal fun get(key: FeatureKey): T? {
|
||||
logger.info { "Getting $featureType: \"$key\"" }
|
||||
val feature = registry.value[key]
|
||||
logger.info {
|
||||
if (feature != null) {
|
||||
"Got $featureType: \"$key\""
|
||||
} else {
|
||||
"$featureType not found: \"$key\""
|
||||
}
|
||||
}
|
||||
return feature
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package io.modelcontextprotocol.kotlin.sdk.server
|
||||
|
||||
import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.GetPromptResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.Prompt
|
||||
import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest
|
||||
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
|
||||
import io.modelcontextprotocol.kotlin.sdk.Resource
|
||||
import io.modelcontextprotocol.kotlin.sdk.Tool
|
||||
|
||||
internal typealias FeatureKey = String
|
||||
|
||||
/**
|
||||
* Represents a feature with an associated unique key.
|
||||
*/
|
||||
internal interface Feature {
|
||||
val key: FeatureKey
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class representing a registered tool on the server.
|
||||
*
|
||||
* @property tool The tool definition.
|
||||
* @property handler A suspend function to handle the tool call requests.
|
||||
*/
|
||||
public data class RegisteredTool(val tool: Tool, val handler: suspend (CallToolRequest) -> CallToolResult) : Feature {
|
||||
override val key: String = tool.name
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class representing a registered prompt on the server.
|
||||
*
|
||||
* @property prompt The prompt definition.
|
||||
* @property messageProvider A suspend function that returns the prompt content when requested by the client.
|
||||
*/
|
||||
public data class RegisteredPrompt(
|
||||
val prompt: Prompt,
|
||||
val messageProvider: suspend (GetPromptRequest) -> GetPromptResult,
|
||||
) : Feature {
|
||||
override val key: String = prompt.name
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class representing a registered resource on the server.
|
||||
*
|
||||
* @property resource The resource definition.
|
||||
* @property readHandler A suspend function to handle read requests for this resource.
|
||||
*/
|
||||
public data class RegisteredResource(
|
||||
val resource: Resource,
|
||||
val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult,
|
||||
) : Feature {
|
||||
override val key: String = resource.uri
|
||||
}
|
||||
|
|
@ -27,12 +27,8 @@ import io.modelcontextprotocol.kotlin.sdk.ToolAnnotations
|
|||
import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions
|
||||
import io.modelcontextprotocol.kotlin.sdk.shared.Transport
|
||||
import kotlinx.atomicfu.atomic
|
||||
import kotlinx.atomicfu.getAndUpdate
|
||||
import kotlinx.atomicfu.update
|
||||
import kotlinx.collections.immutable.minus
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toPersistentSet
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
|
@ -59,7 +55,7 @@ public class ServerOptions(public val capabilities: ServerCapabilities, enforceS
|
|||
* this server. The provider is called each time a new session is started to support dynamic instructions.
|
||||
* @param block A block to configure the mcp server.
|
||||
*/
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
public open class Server(
|
||||
protected val serverInfo: Implementation,
|
||||
protected val options: ServerOptions,
|
||||
|
|
@ -92,15 +88,16 @@ public open class Server(
|
|||
@Suppress("ktlint:standard:backing-property-naming")
|
||||
private var _onClose: () -> Unit = {}
|
||||
|
||||
private val _tools = atomic(persistentMapOf<String, RegisteredTool>())
|
||||
private val _prompts = atomic(persistentMapOf<String, RegisteredPrompt>())
|
||||
private val _resources = atomic(persistentMapOf<String, RegisteredResource>())
|
||||
private val toolRegistry = FeatureRegistry<RegisteredTool>("Tool")
|
||||
private val promptRegistry = FeatureRegistry<RegisteredPrompt>("Prompt")
|
||||
private val resourceRegistry = FeatureRegistry<RegisteredResource>("Resource")
|
||||
|
||||
public val tools: Map<String, RegisteredTool>
|
||||
get() = _tools.value
|
||||
get() = toolRegistry.values
|
||||
public val prompts: Map<String, RegisteredPrompt>
|
||||
get() = _prompts.value
|
||||
get() = promptRegistry.values
|
||||
public val resources: Map<String, RegisteredResource>
|
||||
get() = _resources.value
|
||||
get() = resourceRegistry.values
|
||||
|
||||
init {
|
||||
block(this)
|
||||
|
|
@ -108,7 +105,7 @@ public open class Server(
|
|||
|
||||
public suspend fun close() {
|
||||
logger.debug { "Closing MCP server" }
|
||||
sessions.value.forEach { it.close() }
|
||||
sessions.value.forEach { session -> session.close() }
|
||||
_onClose()
|
||||
}
|
||||
|
||||
|
|
@ -172,12 +169,12 @@ public open class Server(
|
|||
// Register cleanup handler to remove session from list when it closes
|
||||
session.onClose {
|
||||
logger.debug { "Removing closed session from active sessions list" }
|
||||
sessions.update { list -> list - session }
|
||||
sessions.update { list -> list.remove(session) }
|
||||
}
|
||||
logger.debug { "Server session connecting to transport" }
|
||||
session.connect(transport)
|
||||
logger.debug { "Server session successfully connected to transport" }
|
||||
sessions.update { it.add(session) }
|
||||
sessions.update { sessions -> sessions.add(session) }
|
||||
|
||||
_onConnect()
|
||||
return session
|
||||
|
|
@ -229,12 +226,12 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support tools.
|
||||
*/
|
||||
public fun addTool(tool: Tool, handler: suspend (CallToolRequest) -> CallToolResult) {
|
||||
if (options.capabilities.tools == null) {
|
||||
check(options.capabilities.tools != null) {
|
||||
logger.error { "Failed to add tool '${tool.name}': Server does not support tools capability" }
|
||||
throw IllegalStateException("Server does not support tools capability. Enable it in ServerOptions.")
|
||||
"Server does not support tools capability. Enable it in ServerOptions."
|
||||
}
|
||||
logger.info { "Registering tool: ${tool.name}" }
|
||||
_tools.update { current -> current.put(tool.name, RegisteredTool(tool, handler)) }
|
||||
|
||||
toolRegistry.add(RegisteredTool(tool, handler))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -249,6 +246,7 @@ public open class Server(
|
|||
* @param handler A suspend function that handles executing the tool when called by the client.
|
||||
* @throws IllegalStateException If the server does not support tools.
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
public fun addTool(
|
||||
name: String,
|
||||
description: String,
|
||||
|
|
@ -269,12 +267,11 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support tools.
|
||||
*/
|
||||
public fun addTools(toolsToAdd: List<RegisteredTool>) {
|
||||
if (options.capabilities.tools == null) {
|
||||
check(options.capabilities.tools != null) {
|
||||
logger.error { "Failed to add tools: Server does not support tools capability" }
|
||||
throw IllegalStateException("Server does not support tools capability.")
|
||||
"Server does not support tools capability."
|
||||
}
|
||||
logger.info { "Registering ${toolsToAdd.size} tools" }
|
||||
_tools.update { current -> current.putAll(toolsToAdd.associateBy { it.tool.name }) }
|
||||
toolRegistry.addAll(toolsToAdd)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -285,23 +282,11 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support tools.
|
||||
*/
|
||||
public fun removeTool(name: String): Boolean {
|
||||
if (options.capabilities.tools == null) {
|
||||
check(options.capabilities.tools != null) {
|
||||
logger.error { "Failed to remove tool '$name': Server does not support tools capability" }
|
||||
throw IllegalStateException("Server does not support tools capability.")
|
||||
"Server does not support tools capability."
|
||||
}
|
||||
logger.info { "Removing tool: $name" }
|
||||
|
||||
val oldMap = _tools.getAndUpdate { current -> current.remove(name) }
|
||||
|
||||
val removed = name in oldMap
|
||||
logger.debug {
|
||||
if (removed) {
|
||||
"Tool removed: $name"
|
||||
} else {
|
||||
"Tool not found: $name"
|
||||
}
|
||||
}
|
||||
return removed
|
||||
return toolRegistry.remove(name)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -312,22 +297,12 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support tools.
|
||||
*/
|
||||
public fun removeTools(toolNames: List<String>): Int {
|
||||
if (options.capabilities.tools == null) {
|
||||
check(options.capabilities.tools != null) {
|
||||
logger.error { "Failed to remove tools: Server does not support tools capability" }
|
||||
throw IllegalStateException("Server does not support tools capability.")
|
||||
"Server does not support tools capability."
|
||||
}
|
||||
logger.info { "Removing ${toolNames.size} tools" }
|
||||
|
||||
val oldMap = _tools.getAndUpdate { current -> current - toolNames.toPersistentSet() }
|
||||
|
||||
val removedCount = toolNames.count { it in oldMap }
|
||||
logger.info {
|
||||
if (removedCount > 0) {
|
||||
"Removed $removedCount tools"
|
||||
} else {
|
||||
"No tools were removed"
|
||||
}
|
||||
}
|
||||
val removedCount = toolRegistry.removeAll(toolNames)
|
||||
return removedCount
|
||||
}
|
||||
|
||||
|
|
@ -339,12 +314,11 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support prompts.
|
||||
*/
|
||||
public fun addPrompt(prompt: Prompt, promptProvider: suspend (GetPromptRequest) -> GetPromptResult) {
|
||||
if (options.capabilities.prompts == null) {
|
||||
check(options.capabilities.prompts != null) {
|
||||
logger.error { "Failed to add prompt '${prompt.name}': Server does not support prompts capability" }
|
||||
throw IllegalStateException("Server does not support prompts capability.")
|
||||
"Server does not support prompts capability."
|
||||
}
|
||||
logger.info { "Registering prompt: ${prompt.name}" }
|
||||
_prompts.update { current -> current.put(prompt.name, RegisteredPrompt(prompt, promptProvider)) }
|
||||
promptRegistry.add(RegisteredPrompt(prompt, promptProvider))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -373,12 +347,11 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support prompts.
|
||||
*/
|
||||
public fun addPrompts(promptsToAdd: List<RegisteredPrompt>) {
|
||||
if (options.capabilities.prompts == null) {
|
||||
check(options.capabilities.prompts != null) {
|
||||
logger.error { "Failed to add prompts: Server does not support prompts capability" }
|
||||
throw IllegalStateException("Server does not support prompts capability.")
|
||||
"Server does not support prompts capability."
|
||||
}
|
||||
logger.info { "Registering ${promptsToAdd.size} prompts" }
|
||||
_prompts.update { current -> current.putAll(promptsToAdd.associateBy { it.prompt.name }) }
|
||||
promptRegistry.addAll(promptsToAdd)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -389,23 +362,12 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support prompts.
|
||||
*/
|
||||
public fun removePrompt(name: String): Boolean {
|
||||
if (options.capabilities.prompts == null) {
|
||||
check(options.capabilities.prompts != null) {
|
||||
logger.error { "Failed to remove prompt '$name': Server does not support prompts capability" }
|
||||
throw IllegalStateException("Server does not support prompts capability.")
|
||||
"Server does not support prompts capability."
|
||||
}
|
||||
logger.info { "Removing prompt: $name" }
|
||||
|
||||
val oldMap = _prompts.getAndUpdate { current -> current.remove(name) }
|
||||
|
||||
val removed = name in oldMap
|
||||
logger.debug {
|
||||
if (removed) {
|
||||
"Prompt removed: $name"
|
||||
} else {
|
||||
"Prompt not found: $name"
|
||||
}
|
||||
}
|
||||
return removed
|
||||
return promptRegistry.remove(name)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -416,24 +378,12 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support prompts.
|
||||
*/
|
||||
public fun removePrompts(promptNames: List<String>): Int {
|
||||
if (options.capabilities.prompts == null) {
|
||||
check(options.capabilities.prompts != null) {
|
||||
logger.error { "Failed to remove prompts: Server does not support prompts capability" }
|
||||
throw IllegalStateException("Server does not support prompts capability.")
|
||||
"Server does not support prompts capability."
|
||||
}
|
||||
logger.info { "Removing ${promptNames.size} prompts" }
|
||||
|
||||
val oldMap = _prompts.getAndUpdate { current -> current - promptNames.toPersistentSet() }
|
||||
|
||||
val removedCount = promptNames.count { it in oldMap }
|
||||
|
||||
logger.info {
|
||||
if (removedCount > 0) {
|
||||
"Removed $removedCount prompts"
|
||||
} else {
|
||||
"No prompts were removed"
|
||||
}
|
||||
}
|
||||
return removedCount
|
||||
return promptRegistry.removeAll(promptNames)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -453,17 +403,12 @@ public open class Server(
|
|||
mimeType: String = "text/html",
|
||||
readHandler: suspend (ReadResourceRequest) -> ReadResourceResult,
|
||||
) {
|
||||
if (options.capabilities.resources == null) {
|
||||
check(options.capabilities.resources != null) {
|
||||
logger.error { "Failed to add resource '$name': Server does not support resources capability" }
|
||||
throw IllegalStateException("Server does not support resources capability.")
|
||||
}
|
||||
logger.info { "Registering resource: $name ($uri)" }
|
||||
_resources.update { current ->
|
||||
current.put(
|
||||
uri,
|
||||
RegisteredResource(Resource(uri, name, description, mimeType), readHandler),
|
||||
)
|
||||
"Server does not support resources capability."
|
||||
}
|
||||
val resource = Resource(uri, name, description, mimeType)
|
||||
resourceRegistry.add(RegisteredResource(resource, readHandler))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -473,12 +418,11 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support resources.
|
||||
*/
|
||||
public fun addResources(resourcesToAdd: List<RegisteredResource>) {
|
||||
if (options.capabilities.resources == null) {
|
||||
check(options.capabilities.resources != null) {
|
||||
logger.error { "Failed to add resources: Server does not support resources capability" }
|
||||
throw IllegalStateException("Server does not support resources capability.")
|
||||
"Server does not support resources capability."
|
||||
}
|
||||
logger.info { "Registering ${resourcesToAdd.size} resources" }
|
||||
_resources.update { current -> current.putAll(resourcesToAdd.associateBy { it.resource.uri }) }
|
||||
resourceRegistry.addAll(resourcesToAdd)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -489,23 +433,11 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support resources.
|
||||
*/
|
||||
public fun removeResource(uri: String): Boolean {
|
||||
if (options.capabilities.resources == null) {
|
||||
check(options.capabilities.resources != null) {
|
||||
logger.error { "Failed to remove resource '$uri': Server does not support resources capability" }
|
||||
throw IllegalStateException("Server does not support resources capability.")
|
||||
"Server does not support resources capability."
|
||||
}
|
||||
logger.info { "Removing resource: $uri" }
|
||||
|
||||
val oldMap = _resources.getAndUpdate { current -> current.remove(uri) }
|
||||
|
||||
val removed = uri in oldMap
|
||||
logger.debug {
|
||||
if (removed) {
|
||||
"Resource removed: $uri"
|
||||
} else {
|
||||
"Resource not found: $uri"
|
||||
}
|
||||
}
|
||||
return removed
|
||||
return resourceRegistry.remove(uri)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -516,24 +448,11 @@ public open class Server(
|
|||
* @throws IllegalStateException If the server does not support resources.
|
||||
*/
|
||||
public fun removeResources(uris: List<String>): Int {
|
||||
if (options.capabilities.resources == null) {
|
||||
check(options.capabilities.resources != null) {
|
||||
logger.error { "Failed to remove resources: Server does not support resources capability" }
|
||||
throw IllegalStateException("Server does not support resources capability.")
|
||||
"Server does not support resources capability."
|
||||
}
|
||||
logger.info { "Removing ${uris.size} resources" }
|
||||
|
||||
val oldMap = _resources.getAndUpdate { current -> current - uris.toPersistentSet() }
|
||||
|
||||
val removedCount = uris.count { it in oldMap }
|
||||
|
||||
logger.info {
|
||||
if (removedCount > 0) {
|
||||
"Removed $removedCount resources"
|
||||
} else {
|
||||
"No resources were removed"
|
||||
}
|
||||
}
|
||||
return removedCount
|
||||
return resourceRegistry.removeAll(uris)
|
||||
}
|
||||
|
||||
// --- Internal Handlers ---
|
||||
|
|
@ -545,17 +464,17 @@ public open class Server(
|
|||
private suspend fun handleCallTool(request: CallToolRequest): CallToolResult {
|
||||
logger.debug { "Handling tool call request for tool: ${request.name}" }
|
||||
|
||||
// Check if tool exists
|
||||
val tool = _tools.value[request.name]
|
||||
?: run {
|
||||
logger.error { "Tool not found: ${request.name}" }
|
||||
return CallToolResult(
|
||||
content = listOf(TextContent(text = "Tool ${request.name} not found")),
|
||||
isError = true,
|
||||
)
|
||||
}
|
||||
// Check if the tool exists
|
||||
val tool = toolRegistry.get(request.name) ?: run {
|
||||
logger.error { "Tool not found: ${request.name}" }
|
||||
return CallToolResult(
|
||||
content = listOf(TextContent(text = "Tool ${request.name} not found")),
|
||||
isError = true,
|
||||
)
|
||||
}
|
||||
|
||||
// Execute tool handler and catch any errors
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
// Execute the tool handler and catch any errors
|
||||
return try {
|
||||
logger.trace { "Executing tool ${request.name} with input: ${request.arguments}" }
|
||||
tool.handler(request)
|
||||
|
|
@ -577,7 +496,7 @@ public open class Server(
|
|||
|
||||
private suspend fun handleGetPrompt(request: GetPromptRequest): GetPromptResult {
|
||||
logger.debug { "Handling get prompt request for: ${request.name}" }
|
||||
val prompt = prompts[request.name]
|
||||
val prompt = promptRegistry.get(request.name)
|
||||
?: run {
|
||||
logger.error { "Prompt not found: ${request.name}" }
|
||||
throw IllegalArgumentException("Prompt not found: ${request.name}")
|
||||
|
|
@ -592,7 +511,7 @@ public open class Server(
|
|||
|
||||
private suspend fun handleReadResource(request: ReadResourceRequest): ReadResourceResult {
|
||||
logger.debug { "Handling read resource request for: ${request.uri}" }
|
||||
val resource = resources[request.uri]
|
||||
val resource = resourceRegistry.get(request.uri)
|
||||
?: run {
|
||||
logger.error { "Resource not found: ${request.uri}" }
|
||||
throw IllegalArgumentException("Resource not found: ${request.uri}")
|
||||
|
|
@ -605,33 +524,3 @@ public open class Server(
|
|||
return ListResourceTemplatesResult(listOf())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper class representing a registered tool on the server.
|
||||
*
|
||||
* @property tool The tool definition.
|
||||
* @property handler A suspend function to handle the tool call requests.
|
||||
*/
|
||||
public data class RegisteredTool(val tool: Tool, val handler: suspend (CallToolRequest) -> CallToolResult)
|
||||
|
||||
/**
|
||||
* A wrapper class representing a registered prompt on the server.
|
||||
*
|
||||
* @property prompt The prompt definition.
|
||||
* @property messageProvider A suspend function that returns the prompt content when requested by the client.
|
||||
*/
|
||||
public data class RegisteredPrompt(
|
||||
val prompt: Prompt,
|
||||
val messageProvider: suspend (GetPromptRequest) -> GetPromptResult,
|
||||
)
|
||||
|
||||
/**
|
||||
* A wrapper class representing a registered resource on the server.
|
||||
*
|
||||
* @property resource The resource definition.
|
||||
* @property readHandler A suspend function to handle read requests for this resource.
|
||||
*/
|
||||
public data class RegisteredResource(
|
||||
val resource: Resource,
|
||||
val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import kotlin.test.Test
|
|||
import kotlin.test.assertTrue
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class SSeIntegrationTest : AbstractSseIntegrationTest() {
|
||||
class SseIntegrationTest : AbstractSseIntegrationTest() {
|
||||
|
||||
@Test
|
||||
fun `client should be able to connect to sse server`() = runTest(timeout = 5.seconds) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue