Skip to content

Commit 5d60830

Browse files
committed
Move features management to the FeatureRegistry
1 parent f27fe9a commit 5d60830

File tree

3 files changed

+313
-97
lines changed

3 files changed

+313
-97
lines changed
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
package io.modelcontextprotocol.kotlin.sdk.server
2+
3+
import io.github.oshai.kotlinlogging.KotlinLogging
4+
import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
5+
import io.modelcontextprotocol.kotlin.sdk.CallToolResult
6+
import io.modelcontextprotocol.kotlin.sdk.GetPromptRequest
7+
import io.modelcontextprotocol.kotlin.sdk.GetPromptResult
8+
import io.modelcontextprotocol.kotlin.sdk.Prompt
9+
import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest
10+
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
11+
import io.modelcontextprotocol.kotlin.sdk.Resource
12+
import io.modelcontextprotocol.kotlin.sdk.Tool
13+
import kotlinx.atomicfu.atomic
14+
import kotlinx.atomicfu.getAndUpdate
15+
import kotlinx.atomicfu.update
16+
import kotlinx.collections.immutable.minus
17+
import kotlinx.collections.immutable.persistentMapOf
18+
import kotlinx.collections.immutable.toPersistentSet
19+
20+
/**
21+
* FeaturesRegistry is an internal class responsible for managing the registration and retrieval of
22+
* tools [RegisteredTool], prompts [RegisteredPrompt], and resources [RegisteredResource].
23+
* It provides mechanisms to add, remove, and fetch these entities through a thread-safe approach using atomic updates.
24+
*/
25+
internal class FeatureRegistry {
26+
private val logger = KotlinLogging.logger {}
27+
28+
private val toolsRegistry = atomic(persistentMapOf<String, RegisteredTool>())
29+
private val promptsRegistry = atomic(persistentMapOf<String, RegisteredPrompt>())
30+
private val resourcesRegistry = atomic(persistentMapOf<String, RegisteredResource>())
31+
32+
/**
33+
* A read-only map of currently registered tools, where the key is the tool's unique name,
34+
* and the value is the corresponding [RegisteredTool] object containing its definition
35+
* and handler.
36+
*/
37+
internal val tools: Map<String, RegisteredTool>
38+
get() = toolsRegistry.value
39+
40+
/**
41+
* A read-only map of currently registered prompts, where the key is the prompt's unique name,
42+
* and the value is the corresponding [RegisteredPrompt] object containing its definition
43+
* and handler.
44+
*/
45+
internal val prompts: Map<String, RegisteredPrompt>
46+
get() = promptsRegistry.value
47+
48+
/**
49+
* A read-only map of currently registered resources, where the key is the resource's URI,
50+
* and the value is the corresponding [RegisteredResource] object containing its definition
51+
* and handler.
52+
*/
53+
internal val resources: Map<String, RegisteredResource>
54+
get() = resourcesRegistry.value
55+
56+
//region Tools
57+
58+
/**
59+
* Registers a single tool.
60+
*
61+
* @param tool A [io.modelcontextprotocol.kotlin.sdk.Tool] object describing the tool.
62+
* @param handler A suspend function that handles executing the tool when called by the client.
63+
*/
64+
internal fun addTool(tool: Tool, handler: suspend (CallToolRequest) -> CallToolResult) {
65+
logger.info { "Registering tool: ${tool.name}" }
66+
toolsRegistry.update { current -> current.put(tool.name, RegisteredTool(tool, handler)) }
67+
}
68+
69+
/**
70+
* Registers multiple tools at once.
71+
*
72+
* @param tools A list of [RegisteredTool] objects representing the tools to register.
73+
*/
74+
internal fun addTools(tools: List<RegisteredTool>) {
75+
logger.info { "Registering ${tools.size} tools" }
76+
toolsRegistry.update { current -> current.putAll(tools.associateBy { it.tool.name }) }
77+
}
78+
79+
/**
80+
* Removes a tool from the registry by its name.
81+
*
82+
* @param toolName The name of the tool to be removed.
83+
* @return True if the tool was successfully removed, false if the tool was not found.
84+
*/
85+
internal fun removeTool(toolName: String): Boolean {
86+
logger.info { "Unregistering tool: $toolName" }
87+
val oldMap = toolsRegistry.getAndUpdate { current -> current.remove(toolName) }
88+
89+
return toolName in oldMap
90+
}
91+
92+
/**
93+
* Removes all tools from the registry that match the provided list of tool names.
94+
*
95+
* @param toolNames A list of names of the tools to be removed from the registry.
96+
* @return The number of tools that were successfully removed.
97+
*/
98+
internal fun removeTools(toolNames: List<String>): Int {
99+
logger.info { "Unregistering ${toolNames.size} tools" }
100+
val oldMap = toolsRegistry.getAndUpdate { current -> current - toolNames.toPersistentSet() }
101+
102+
val removedCount = toolNames.count { it in oldMap }
103+
104+
return removedCount
105+
}
106+
107+
internal fun getTool(toolName: String): RegisteredTool? = toolsRegistry.value[toolName]
108+
109+
//endregion Tools
110+
111+
//region Prompts
112+
113+
/**
114+
* Registers a single prompt.
115+
*
116+
* @param prompt A [Prompt] object describing the prompt.
117+
* @param promptProvider A suspend function that returns the prompt content when requested by the client.
118+
*/
119+
internal fun addPrompt(prompt: Prompt, promptProvider: suspend (GetPromptRequest) -> GetPromptResult) {
120+
logger.info { "Registering prompt: ${prompt.name}" }
121+
promptsRegistry.update { current -> current.put(prompt.name, RegisteredPrompt(prompt, promptProvider)) }
122+
}
123+
124+
/**
125+
* Registers multiple prompts at once.
126+
*
127+
* @param prompts A list of [RegisteredPrompt] objects representing the prompts to register.
128+
*/
129+
internal fun addPrompts(prompts: List<RegisteredPrompt>) {
130+
logger.info { "Registering ${prompts.size} prompts" }
131+
promptsRegistry.update { current -> current.putAll(prompts.associateBy { it.prompt.name }) }
132+
}
133+
134+
/**
135+
* Removes a prompt from the registry by its name.
136+
*
137+
* @param promptName The name of the prompt to be removed.
138+
* @return True if the prompt was successfully removed, false if the prompt was not found.
139+
*/
140+
internal fun removePrompt(promptName: String): Boolean {
141+
logger.info { "Unregistering prompt: $promptName" }
142+
val oldMap = promptsRegistry.getAndUpdate { current -> current.remove(promptName) }
143+
144+
return promptName in oldMap
145+
}
146+
147+
/**
148+
* Removes all prompts from the registry that match the provided list of prompt names.
149+
*
150+
* @param promptNames A list of names of the prompts to be removed from the registry.
151+
* @return The number of prompts that were successfully removed.
152+
*/
153+
internal fun removePrompts(promptNames: List<String>): Int {
154+
logger.info { "Unregistering ${promptNames.size} prompts" }
155+
val oldMap = promptsRegistry.getAndUpdate { current -> current - promptNames.toPersistentSet() }
156+
157+
val removedCount = promptNames.count { it in oldMap }
158+
159+
return removedCount
160+
}
161+
162+
internal fun getPrompt(promptName: String): RegisteredPrompt? = promptsRegistry.value[promptName]
163+
164+
//endregion Prompts
165+
166+
//region Resources
167+
168+
/**
169+
* Registers a single resource.
170+
*
171+
* @param resource A [Resource] object describing the resource.
172+
* @param readHandler A suspend function that handles reading the resource when requested by the client.
173+
*/
174+
internal fun addResource(resource: Resource, readHandler: suspend (ReadResourceRequest) -> ReadResourceResult) {
175+
logger.info { "Registering resource: ${resource.name} (${resource.uri})" }
176+
resourcesRegistry.update { current -> current.put(resource.uri, RegisteredResource(resource, readHandler)) }
177+
}
178+
179+
/**
180+
* Registers multiple resources at once.
181+
*
182+
* @param resources A list of [RegisteredResource] objects representing the resources to register.
183+
*/
184+
internal fun addResources(resources: List<RegisteredResource>) {
185+
logger.info { "Registering ${resources.size} resources" }
186+
resourcesRegistry.update { current -> current.putAll(resources.associateBy { it.resource.uri }) }
187+
}
188+
189+
/**
190+
* Removes a resource from the registry by its URI.
191+
*
192+
* @param resourceUri The URI of the resource to be removed.
193+
* @return True if the resource was successfully removed, false if the resource was not found.
194+
*/
195+
internal fun removeResource(resourceUri: String): Boolean {
196+
logger.info { "Unregistering resource: $resourceUri" }
197+
val oldMap = resourcesRegistry.getAndUpdate { current -> current.remove(resourceUri) }
198+
199+
return resourceUri in oldMap
200+
}
201+
202+
/**
203+
* Removes all resources from the registry that match the provided list of resource URIs.
204+
*
205+
* @param resourceUris A list of URIs of the resources to be removed from the registry.
206+
* @return The number of resources that were successfully removed.
207+
*/
208+
internal fun removeResources(resourceUris: List<String>): Int {
209+
logger.info { "Unregistering ${resourceUris.size} resources" }
210+
val oldMap = resourcesRegistry.getAndUpdate { current -> current - resourceUris.toPersistentSet() }
211+
212+
val removedCount = resourceUris.count { it in oldMap }
213+
214+
return removedCount
215+
}
216+
217+
internal fun getResource(resourceUri: String): RegisteredResource? = resourcesRegistry.value[resourceUri]
218+
219+
//endregion Resources
220+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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+
/**
14+
* A wrapper class representing a registered tool on the server.
15+
*
16+
* @property tool The tool definition.
17+
* @property handler A suspend function to handle the tool call requests.
18+
*/
19+
public data class RegisteredTool(val tool: Tool, val handler: suspend (CallToolRequest) -> CallToolResult)
20+
21+
/**
22+
* A wrapper class representing a registered prompt on the server.
23+
*
24+
* @property prompt The prompt definition.
25+
* @property messageProvider A suspend function that returns the prompt content when requested by the client.
26+
*/
27+
public data class RegisteredPrompt(
28+
val prompt: Prompt,
29+
val messageProvider: suspend (GetPromptRequest) -> GetPromptResult,
30+
)
31+
32+
/**
33+
* A wrapper class representing a registered resource on the server.
34+
*
35+
* @property resource The resource definition.
36+
* @property readHandler A suspend function to handle read requests for this resource.
37+
*/
38+
public data class RegisteredResource(
39+
val resource: Resource,
40+
val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult,
41+
)

0 commit comments

Comments
 (0)