Skip to content

Commit 913c0d1

Browse files
authored
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
1 parent ee341df commit 913c0d1

File tree

5 files changed

+245
-177
lines changed

5 files changed

+245
-177
lines changed

kotlin-sdk-server/api/kotlin-sdk-server.api

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,43 @@ public final class io/modelcontextprotocol/kotlin/sdk/server/KtorServerKt {
55
public static final fun mcp (Lio/ktor/server/routing/Routing;Lkotlin/jvm/functions/Function1;)V
66
}
77

8-
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt {
8+
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt : io/modelcontextprotocol/kotlin/sdk/server/Feature {
99
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)V
1010
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Prompt;
1111
public final fun component2 ()Lkotlin/jvm/functions/Function2;
1212
public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Prompt;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredPrompt;
1313
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;
1414
public fun equals (Ljava/lang/Object;)Z
15+
public fun getKey ()Ljava/lang/String;
1516
public final fun getMessageProvider ()Lkotlin/jvm/functions/Function2;
1617
public final fun getPrompt ()Lio/modelcontextprotocol/kotlin/sdk/Prompt;
1718
public fun hashCode ()I
1819
public fun toString ()Ljava/lang/String;
1920
}
2021

21-
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredResource {
22+
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredResource : io/modelcontextprotocol/kotlin/sdk/server/Feature {
2223
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;)V
2324
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Resource;
2425
public final fun component2 ()Lkotlin/jvm/functions/Function2;
2526
public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Resource;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredResource;
2627
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;
2728
public fun equals (Ljava/lang/Object;)Z
29+
public fun getKey ()Ljava/lang/String;
2830
public final fun getReadHandler ()Lkotlin/jvm/functions/Function2;
2931
public final fun getResource ()Lio/modelcontextprotocol/kotlin/sdk/Resource;
3032
public fun hashCode ()I
3133
public fun toString ()Ljava/lang/String;
3234
}
3335

34-
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool {
36+
public final class io/modelcontextprotocol/kotlin/sdk/server/RegisteredTool : io/modelcontextprotocol/kotlin/sdk/server/Feature {
3537
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)V
3638
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/Tool;
3739
public final fun component2 ()Lkotlin/jvm/functions/Function2;
3840
public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/Tool;Lkotlin/jvm/functions/Function2;)Lio/modelcontextprotocol/kotlin/sdk/server/RegisteredTool;
3941
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;
4042
public fun equals (Ljava/lang/Object;)Z
4143
public final fun getHandler ()Lkotlin/jvm/functions/Function2;
44+
public fun getKey ()Ljava/lang/String;
4245
public final fun getTool ()Lio/modelcontextprotocol/kotlin/sdk/Tool;
4346
public fun hashCode ()I
4447
public fun toString ()Ljava/lang/String;
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package io.modelcontextprotocol.kotlin.sdk.server
2+
3+
import io.github.oshai.kotlinlogging.KotlinLogging
4+
import kotlinx.atomicfu.atomic
5+
import kotlinx.atomicfu.getAndUpdate
6+
import kotlinx.atomicfu.update
7+
import kotlinx.collections.immutable.minus
8+
import kotlinx.collections.immutable.persistentMapOf
9+
import kotlinx.collections.immutable.toPersistentSet
10+
11+
/**
12+
* A generic registry for managing features of a specified type. This class provides thread-safe
13+
* operations for adding, removing, and retrieving features from the registry.
14+
*
15+
* @param T The type of the feature, constrained to implement the [Feature] interface.
16+
* @param featureType A string description of the type of feature being managed.
17+
* Used primarily for logging purposes.
18+
*/
19+
internal class FeatureRegistry<T : Feature>(private val featureType: String) {
20+
21+
private val logger = KotlinLogging.logger(name = "FeatureRegistry[$featureType]")
22+
23+
/**
24+
* Atomic variable used to maintain a thread-safe registry of features.
25+
* Stores a persistent map where each feature is identified by a unique key.
26+
*/
27+
private val registry = atomic(persistentMapOf<FeatureKey, T>())
28+
29+
/**
30+
* Provides a snapshot of all features currently registered in the registry.
31+
* Keys represent unique identifiers for each feature, and values represent the associated features.
32+
*/
33+
internal val values: Map<FeatureKey, T>
34+
get() = registry.value
35+
36+
/**
37+
* Adds the specified feature to the registry.
38+
*
39+
* @param feature The feature to be added to the registry.
40+
*/
41+
internal fun add(feature: T) {
42+
logger.info { "Adding $featureType: \"${feature.key}\"" }
43+
registry.update { current -> current.put(feature.key, feature) }
44+
logger.info { "Added $featureType: \"${feature.key}\"" }
45+
}
46+
47+
/**
48+
* Adds the given list of features to the registry. Each feature is mapped by its key
49+
* and added to the current registry.
50+
*
51+
* @param features The list of features to add to the registry.
52+
*/
53+
internal fun addAll(features: List<T>) {
54+
logger.info { "Adding ${featureType}s: ${features.size}" }
55+
registry.update { current -> current.putAll(features.associateBy { it.key }) }
56+
logger.info { "Added ${featureType}s: ${features.size}" }
57+
}
58+
59+
/**
60+
* Removes the feature associated with the given key from the registry.
61+
*
62+
* @param key The key of the feature to be removed.
63+
* @return `true` if the feature was successfully removed, or `false` if the feature was not found.
64+
*/
65+
internal fun remove(key: FeatureKey): Boolean {
66+
logger.info { "Removing $featureType: \"$key\"" }
67+
val oldMap = registry.getAndUpdate { current -> current.remove(key) }
68+
69+
val removed = key in oldMap
70+
logger.info {
71+
if (removed) {
72+
"Removed $featureType: \"$key\""
73+
} else {
74+
"$featureType not found: \"$key\""
75+
}
76+
}
77+
return key in oldMap
78+
}
79+
80+
/**
81+
* Removes the features associated with the given keys from the registry.
82+
*
83+
* @param keys The list of keys whose associated features are to be removed.
84+
* @return The number of features that were successfully removed.
85+
*/
86+
internal fun removeAll(keys: List<FeatureKey>): Int {
87+
logger.info { "Removing ${featureType}s: ${keys.size}" }
88+
val oldMap = registry.getAndUpdate { current -> current - keys.toPersistentSet() }
89+
90+
val removedCount = keys.count { it in oldMap }
91+
logger.info {
92+
if (removedCount > 0) {
93+
"Removed ${featureType}s: $removedCount"
94+
} else {
95+
"No $featureType were removed"
96+
}
97+
}
98+
99+
return removedCount
100+
}
101+
102+
/**
103+
* Retrieves the feature associated with the given key from the registry.
104+
*
105+
* @param key The key of the feature to retrieve.
106+
* @return The feature associated with the given key, or `null` if no such feature exists in the registry.
107+
*/
108+
internal fun get(key: FeatureKey): T? {
109+
logger.info { "Getting $featureType: \"$key\"" }
110+
val feature = registry.value[key]
111+
logger.info {
112+
if (feature != null) {
113+
"Got $featureType: \"$key\""
114+
} else {
115+
"$featureType not found: \"$key\""
116+
}
117+
}
118+
return feature
119+
}
120+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.modelcontextprotocol.kotlin.sdk.server
2+
3+
import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
4+
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
5+
import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest
6+
import io.modelcontextprotocol.kotlin.sdk.GetPromptResult
7+
import io.modelcontextprotocol.kotlin.sdk.Prompt
8+
import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest
9+
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
10+
import io.modelcontextprotocol.kotlin.sdk.Resource
11+
import io.modelcontextprotocol.kotlin.sdk.Tool
12+
13+
internal typealias FeatureKey = String
14+
15+
/**
16+
* Represents a feature with an associated unique key.
17+
*/
18+
internal interface Feature {
19+
val key: FeatureKey
20+
}
21+
22+
/**
23+
* A wrapper class representing a registered tool on the server.
24+
*
25+
* @property tool The tool definition.
26+
* @property handler A suspend function to handle the tool call requests.
27+
*/
28+
public data class RegisteredTool(val tool: Tool, val handler: suspend (CallToolRequest) -> CallToolResult) : Feature {
29+
override val key: String = tool.name
30+
}
31+
32+
/**
33+
* A wrapper class representing a registered prompt on the server.
34+
*
35+
* @property prompt The prompt definition.
36+
* @property messageProvider A suspend function that returns the prompt content when requested by the client.
37+
*/
38+
public data class RegisteredPrompt(
39+
val prompt: Prompt,
40+
val messageProvider: suspend (GetPromptRequest) -> GetPromptResult,
41+
) : Feature {
42+
override val key: String = prompt.name
43+
}
44+
45+
/**
46+
* A wrapper class representing a registered resource on the server.
47+
*
48+
* @property resource The resource definition.
49+
* @property readHandler A suspend function to handle read requests for this resource.
50+
*/
51+
public data class RegisteredResource(
52+
val resource: Resource,
53+
val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult,
54+
) : Feature {
55+
override val key: String = resource.uri
56+
}

0 commit comments

Comments
 (0)