Skip to content

Commit 2e39718

Browse files
KuechAdevcrocod
authored andcommitted
Add parsing of the values passed in resource templates
1 parent d0443e5 commit 2e39718

3 files changed

Lines changed: 44 additions & 12 deletions

File tree

kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Feature.kt

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ import io.modelcontextprotocol.kotlin.sdk.types.Resource
1111
import io.modelcontextprotocol.kotlin.sdk.types.ResourceTemplate
1212
import io.modelcontextprotocol.kotlin.sdk.types.Tool
1313

14+
public interface UriTemplateArgumentExtractor {
15+
/**
16+
* Extracts the arguments from the given [input] string based on the URI template defined in this extractor.
17+
*
18+
* @param input The input string to extract arguments from.
19+
* @return A map of argument names to their corresponding values extracted from the input string.
20+
* If the input does not match the URI template, an empty map is returned.
21+
*/
22+
public fun extractArguments(input: String): Map<String, String>
23+
}
24+
1425
/**
1526
* Represents a unique key for a feature and allows comparing inputs with the [key].
1627
*/
@@ -51,8 +62,11 @@ public class StringFeatureKey(override val key: String) : FeatureKey<String>() {
5162
*
5263
* @property key The URI template string key identifying the feature.
5364
*/
54-
public class UriTemplateFeatureKey(override val key: String) : FeatureKey<Regex>() {
65+
public class UriTemplateFeatureKey(override val key: String) :
66+
FeatureKey<Regex>(),
67+
UriTemplateArgumentExtractor {
5568
override val value: Regex
69+
public val groups: MutableList<String> = mutableListOf<String>()
5670
init {
5771
// Convert URI template to regex as follows:
5872
// - A simple variable `{variable}` is replaced with `(?<variable>[^/]+)`
@@ -62,9 +76,10 @@ public class UriTemplateFeatureKey(override val key: String) : FeatureKey<Regex>
6276

6377
val newRegex = Regex("\\{(<groupName>[^}*]*)(<wildcard>[*]?)}")
6478
.replace(uriWithoutQueryParameters) { matchResult ->
65-
val groupName = matchResult.groups["groupName"]
79+
val groupName = matchResult.groups["groupName"]?.value
6680
checkNotNull(groupName) { "Invalid URI template: $this" }
67-
if (matchResult.groups["wildcard"] != null) {
81+
groups.add(groupName)
82+
if (matchResult.groups["wildcard"]?.value != null) {
6883
"(?<$groupName>.+)"
6984
} else {
7085
"(?<$groupName>[^/]*)"
@@ -74,8 +89,25 @@ public class UriTemplateFeatureKey(override val key: String) : FeatureKey<Regex>
7489
}
7590

7691
override fun matches(input: String): Boolean = value.matches(input)
92+
93+
override fun extractArguments(input: String): TemplateValues {
94+
val matchGroups = value.matchEntire(input)?.groups
95+
return groups.mapNotNull { groupName ->
96+
val groupValue = matchGroups?.get(groupName)?.value
97+
if (groupValue != null) {
98+
groupName to groupValue
99+
} else {
100+
null
101+
}
102+
}.toMap()
103+
}
77104
}
78105

106+
/**
107+
* A map of template variable names to their corresponding values extracted from an input string based on a URI template.
108+
*/
109+
public typealias TemplateValues = Map<String, String>
110+
79111
/**
80112
* The [FeatureKey] used for [Tool]s.
81113
*/
@@ -148,7 +180,7 @@ public data class RegisteredResource(
148180
*/
149181
public data class RegisteredResourceTemplate(
150182
val resourceTemplate: ResourceTemplate,
151-
val readHandler: suspend (ReadResourceRequest) -> ReadResourceResult,
183+
val readHandler: suspend (ReadResourceRequest, TemplateValues) -> ReadResourceResult,
152184
) : Feature<ResourceTemplateFeatureKey> {
153185
override val key: ResourceTemplateFeatureKey = UriTemplateFeatureKey(resourceTemplate.uriTemplate)
154186
}

kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/FeatureRegistry.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,9 @@ internal class FeatureRegistry<T : Feature<S>, S : FeatureKey<R>, R>(private val
143143
* @param key The key of the feature to retrieve.
144144
* @return The feature associated with the given key, or `null` if no such feature exists in the registry.
145145
*/
146-
internal fun get(key: String): T? {
146+
internal fun get(key: String): Map.Entry<S, T>? {
147147
logger.info { "Getting $featureType: \"$key\"" }
148-
val feature = registry.value.entries.singleOrNull { it.key.matches(key) }?.value
148+
val feature = registry.value.entries.singleOrNull { it.key.matches(key) }
149149
if (feature != null) {
150150
logger.info { "Got $featureType: \"$key\"" }
151151
} else {

kotlin-sdk-server/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/Server.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ public open class Server(
569569
name: String,
570570
description: String,
571571
mimeType: String = "text/html",
572-
readHandler: suspend (ReadResourceRequest) -> ReadResourceResult,
572+
readHandler: suspend (ReadResourceRequest, TemplateValues) -> ReadResourceResult,
573573
) {
574574
check(options.capabilities.resources != null) {
575575
logger.error { "Failed to add resource template '$name': Server does not support resources capability" }
@@ -653,7 +653,7 @@ public open class Server(
653653
logger.debug { "Handling tool call request for tool: ${requestParams.name}" }
654654

655655
// Check if the tool exists
656-
val tool = toolRegistry.get(requestParams.name) ?: run {
656+
val tool = toolRegistry.get(requestParams.name)?.value ?: run {
657657
logger.error { "Tool not found: ${requestParams.name}" }
658658
return CallToolResult(
659659
content = listOf(TextContent(text = "Tool ${requestParams.name} not found")),
@@ -684,7 +684,7 @@ public open class Server(
684684
private suspend fun handleGetPrompt(request: GetPromptRequest): GetPromptResult {
685685
val requestParams = request.params
686686
logger.debug { "Handling get prompt request for: ${requestParams.name}" }
687-
val prompt = promptRegistry.get(requestParams.name)
687+
val prompt = promptRegistry.get(requestParams.name)?.value
688688
?: run {
689689
logger.error { "Prompt not found: ${requestParams.name}" }
690690
throw IllegalArgumentException("Prompt not found: ${requestParams.name}")
@@ -700,15 +700,15 @@ public open class Server(
700700
private suspend fun handleReadResource(request: ReadResourceRequest): ReadResourceResult {
701701
val requestParams = request.params
702702
logger.debug { "Handling read resource request for: ${requestParams.uri}" }
703-
val resource = resourceRegistry.get(requestParams.uri)
703+
val resource = resourceRegistry.get(requestParams.uri)?.value
704704
?: run {
705705
logger.debug { "Resource not found: ${requestParams.uri}" }
706-
val resourceTemplate = resourceTemplateRegistry.get(requestParams.uri)
706+
val (templateParser, resourceTemplate) = resourceTemplateRegistry.get(requestParams.uri)
707707
?: run {
708708
logger.error { "Resource or resource template not found: ${requestParams.uri}" }
709709
throw IllegalArgumentException("Resource or resource template not found: ${requestParams.uri}")
710710
}
711-
return resourceTemplate.readHandler(request)
711+
return resourceTemplate.readHandler(request, templateParser.extractArguments(requestParams.uri))
712712
}
713713
return resource.readHandler(request)
714714
}

0 commit comments

Comments
 (0)