Skip to content

Commit 2c9eb29

Browse files
committed
Move features management from Server to the FeatureRegistry
Add Feature interface Make FeatureRegistry generic
1 parent ee341df commit 2c9eb29

File tree

5 files changed

+243
-173
lines changed

5 files changed

+243
-173
lines changed
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)