feat(core): add extensions field to ClientCapabilities and ServerCapabilities (#678)
<!-- Provide a brief summary of your changes -->
Add support for MCP extension negotiation during the initialize
handshake, as defined in the MCP extensions specification. Clients and
servers can now advertise supported extensions via a new `extensions:
Map<String, JsonObject>?` field on their respective capabilities
objects.
## Motivation and Context
<!-- Why is this change needed? What problem does it solve? -->
Extensions are supported part of the [MCP spec
](https://modelcontextprotocol.io/extensions/overview) and are available
in other language libraries e.g. [Golang
](862d78a265/mcp/protocol.go (L215)).
## How Has This Been Tested?
Existing and added unit tests
## Breaking Changes
<!-- Will users need to update their code or configurations? -->
Adding a new field to existing ClientCapabilities and ServerCapabilities
fields will break already compiled clients. They will need to recompile
on upgrading.
## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply. -->
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed
## Additional context
<!-- Add any other context, implementation notes, or design decisions
-->
Co-authored-by: Pavel Gorgulov <devcrocod@gmail.com>
This commit is contained in:
parent
f6ad332228
commit
bf8dc6d6b1
6 changed files with 217 additions and 8 deletions
|
|
@ -606,17 +606,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio
|
||||||
public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities {
|
public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities {
|
||||||
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion;
|
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion;
|
||||||
public fun <init> ()V
|
public fun <init> ()V
|
||||||
public fun <init> (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V
|
public fun <init> (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V
|
||||||
public synthetic fun <init> (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
public synthetic fun <init> (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
|
public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;
|
public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;
|
||||||
public final fun component3 ()Lkotlinx/serialization/json/JsonObject;
|
public final fun component3 ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public final fun component4 ()Lkotlinx/serialization/json/JsonObject;
|
public final fun component4 ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public final fun copy (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
|
public final fun component5 ()Ljava/util/Map;
|
||||||
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
|
public final fun copy (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
|
||||||
|
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities;
|
||||||
public fun equals (Ljava/lang/Object;)Z
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject;
|
public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject;
|
public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject;
|
||||||
|
public final fun getExtensions ()Ljava/util/Map;
|
||||||
public final fun getRoots ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;
|
public final fun getRoots ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;
|
||||||
public final fun getSampling ()Lkotlinx/serialization/json/JsonObject;
|
public final fun getSampling ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public fun hashCode ()I
|
public fun hashCode ()I
|
||||||
|
|
@ -676,6 +678,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBu
|
||||||
public final fun elicitation (Lkotlinx/serialization/json/JsonObject;)V
|
public final fun elicitation (Lkotlinx/serialization/json/JsonObject;)V
|
||||||
public final fun experimental (Lkotlin/jvm/functions/Function1;)V
|
public final fun experimental (Lkotlin/jvm/functions/Function1;)V
|
||||||
public final fun experimental (Lkotlinx/serialization/json/JsonObject;)V
|
public final fun experimental (Lkotlinx/serialization/json/JsonObject;)V
|
||||||
|
public final fun extensions (Ljava/util/Map;)V
|
||||||
public final fun roots (Ljava/lang/Boolean;)V
|
public final fun roots (Ljava/lang/Boolean;)V
|
||||||
public static synthetic fun roots$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder;Ljava/lang/Boolean;ILjava/lang/Object;)V
|
public static synthetic fun roots$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder;Ljava/lang/Boolean;ILjava/lang/Object;)V
|
||||||
public final fun sampling (Lkotlin/jvm/functions/Function1;)V
|
public final fun sampling (Lkotlin/jvm/functions/Function1;)V
|
||||||
|
|
@ -4245,19 +4248,21 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt {
|
||||||
public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities {
|
public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities {
|
||||||
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Companion;
|
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Companion;
|
||||||
public fun <init> ()V
|
public fun <init> ()V
|
||||||
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V
|
public fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)V
|
||||||
public synthetic fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
public synthetic fun <init> (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;
|
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;
|
||||||
public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;
|
public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;
|
||||||
public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;
|
public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;
|
||||||
public final fun component4 ()Lkotlinx/serialization/json/JsonObject;
|
public final fun component4 ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public final fun component5 ()Lkotlinx/serialization/json/JsonObject;
|
public final fun component5 ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public final fun component6 ()Lkotlinx/serialization/json/JsonObject;
|
public final fun component6 ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
|
public final fun component7 ()Ljava/util/Map;
|
||||||
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
|
public final fun copy (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
|
||||||
|
public static synthetic fun copy$default (Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;Ljava/util/Map;ILjava/lang/Object;)Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities;
|
||||||
public fun equals (Ljava/lang/Object;)Z
|
public fun equals (Ljava/lang/Object;)Z
|
||||||
public final fun getCompletions ()Lkotlinx/serialization/json/JsonObject;
|
public final fun getCompletions ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject;
|
public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject;
|
||||||
|
public final fun getExtensions ()Ljava/util/Map;
|
||||||
public final fun getLogging ()Lkotlinx/serialization/json/JsonObject;
|
public final fun getLogging ()Lkotlinx/serialization/json/JsonObject;
|
||||||
public final fun getPrompts ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;
|
public final fun getPrompts ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;
|
||||||
public final fun getResources ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;
|
public final fun getResources ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() {
|
||||||
private var sampling: JsonObject? = null
|
private var sampling: JsonObject? = null
|
||||||
private var roots: ClientCapabilities.Roots? = null
|
private var roots: ClientCapabilities.Roots? = null
|
||||||
private var elicitation: JsonObject? = null
|
private var elicitation: JsonObject? = null
|
||||||
|
private var extensions: Map<String, JsonObject>? = null
|
||||||
private var experimental: JsonObject? = null
|
private var experimental: JsonObject? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -132,6 +133,30 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() {
|
||||||
*/
|
*/
|
||||||
public fun elicitation(block: JsonObjectBuilder.() -> Unit): Unit = elicitation(buildJsonObject(block))
|
public fun elicitation(block: JsonObjectBuilder.() -> Unit): Unit = elicitation(buildJsonObject(block))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines extensions that the client supports.
|
||||||
|
*
|
||||||
|
* Extension identifiers use the format `{vendor-prefix}/{extension-name}`,
|
||||||
|
* e.g., `"io.modelcontextprotocol/ui"`. Each value is an extension-specific
|
||||||
|
* settings object; an empty [JsonObject] indicates no settings.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* ```kotlin
|
||||||
|
* capabilities {
|
||||||
|
* extensions(mapOf(
|
||||||
|
* "io.modelcontextprotocol/ui" to buildJsonObject {
|
||||||
|
* put("mimeTypes", JsonArray(listOf(JsonPrimitive("text/html"))))
|
||||||
|
* }
|
||||||
|
* ))
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param value The map of extension identifiers to their settings
|
||||||
|
*/
|
||||||
|
public fun extensions(value: Map<String, JsonObject>) {
|
||||||
|
this.extensions = value
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines experimental, non-standard capabilities that the client supports.
|
* Defines experimental, non-standard capabilities that the client supports.
|
||||||
*
|
*
|
||||||
|
|
@ -177,6 +202,7 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() {
|
||||||
sampling = sampling,
|
sampling = sampling,
|
||||||
roots = roots,
|
roots = roots,
|
||||||
elicitation = elicitation,
|
elicitation = elicitation,
|
||||||
|
extensions = extensions,
|
||||||
experimental = experimental,
|
experimental = experimental,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,9 @@ public data class Implementation(
|
||||||
* @property elicitation Present if the client supports elicitation from the server.
|
* @property elicitation Present if the client supports elicitation from the server.
|
||||||
* @property experimental Experimental, non-standard capabilities that the client supports.
|
* @property experimental Experimental, non-standard capabilities that the client supports.
|
||||||
* Keys are capability names, values are capability-specific configuration objects.
|
* Keys are capability names, values are capability-specific configuration objects.
|
||||||
|
* @property extensions Optional extensions that the client supports.
|
||||||
|
* Keys are extension identifiers (e.g., `"io.modelcontextprotocol/ui"`),
|
||||||
|
* values are extension-specific settings objects.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
public data class ClientCapabilities(
|
public data class ClientCapabilities(
|
||||||
|
|
@ -52,6 +55,7 @@ public data class ClientCapabilities(
|
||||||
public val roots: Roots? = null,
|
public val roots: Roots? = null,
|
||||||
public val elicitation: JsonObject? = null,
|
public val elicitation: JsonObject? = null,
|
||||||
public val experimental: JsonObject? = null,
|
public val experimental: JsonObject? = null,
|
||||||
|
public val extensions: Map<String, JsonObject>? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -93,6 +97,9 @@ public data class ClientCapabilities(
|
||||||
* @property completions Present if the server supports argument autocompletion suggestions.
|
* @property completions Present if the server supports argument autocompletion suggestions.
|
||||||
* Keys are capability names, values are capability-specific configuration objects.
|
* Keys are capability names, values are capability-specific configuration objects.
|
||||||
* @property experimental Experimental, non-standard capabilities that the server supports.
|
* @property experimental Experimental, non-standard capabilities that the server supports.
|
||||||
|
* @property extensions Optional extensions that the server supports.
|
||||||
|
* Keys are extension identifiers (e.g., `"io.modelcontextprotocol/ui"`),
|
||||||
|
* values are extension-specific settings objects.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
public data class ServerCapabilities(
|
public data class ServerCapabilities(
|
||||||
|
|
@ -102,6 +109,7 @@ public data class ServerCapabilities(
|
||||||
val logging: JsonObject? = null,
|
val logging: JsonObject? = null,
|
||||||
val completions: JsonObject? = null,
|
val completions: JsonObject? = null,
|
||||||
val experimental: JsonObject? = null,
|
val experimental: JsonObject? = null,
|
||||||
|
val extensions: Map<String, JsonObject>? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,53 @@ class CapabilitiesTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should serialize ClientCapabilities with extensions`() {
|
||||||
|
val extensions = mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to buildJsonObject {
|
||||||
|
put("mimeTypes", buildJsonObject { put("text/html", true) })
|
||||||
|
},
|
||||||
|
)
|
||||||
|
val capabilities = ClientCapabilities(
|
||||||
|
extensions = extensions,
|
||||||
|
)
|
||||||
|
verifySerialization(
|
||||||
|
capabilities,
|
||||||
|
McpJson,
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"io.modelcontextprotocol/ui": {
|
||||||
|
"mimeTypes": {
|
||||||
|
"text/html": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should serialize ClientCapabilities with empty extensions settings`() {
|
||||||
|
val capabilities = ClientCapabilities(
|
||||||
|
extensions = mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to EmptyJsonObject,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
verifySerialization(
|
||||||
|
capabilities,
|
||||||
|
McpJson,
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"io.modelcontextprotocol/ui": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should serialize ClientCapabilities with experimental`() {
|
fun `should serialize ClientCapabilities with experimental`() {
|
||||||
val experimental = buildJsonObject {
|
val experimental = buildJsonObject {
|
||||||
|
|
@ -215,11 +262,15 @@ class CapabilitiesTest {
|
||||||
val experimental = buildJsonObject {
|
val experimental = buildJsonObject {
|
||||||
put("feature1", buildJsonObject { put("enabled", true) })
|
put("feature1", buildJsonObject { put("enabled", true) })
|
||||||
}
|
}
|
||||||
|
val extensions = mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to EmptyJsonObject,
|
||||||
|
)
|
||||||
val capabilities = ClientCapabilities(
|
val capabilities = ClientCapabilities(
|
||||||
sampling = ClientCapabilities.sampling,
|
sampling = ClientCapabilities.sampling,
|
||||||
roots = ClientCapabilities.Roots(listChanged = true),
|
roots = ClientCapabilities.Roots(listChanged = true),
|
||||||
elicitation = ClientCapabilities.elicitation,
|
elicitation = ClientCapabilities.elicitation,
|
||||||
experimental = experimental,
|
experimental = experimental,
|
||||||
|
extensions = extensions,
|
||||||
)
|
)
|
||||||
verifySerialization(
|
verifySerialization(
|
||||||
capabilities,
|
capabilities,
|
||||||
|
|
@ -235,6 +286,9 @@ class CapabilitiesTest {
|
||||||
"feature1": {
|
"feature1": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"extensions": {
|
||||||
|
"io.modelcontextprotocol/ui": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
|
|
@ -270,6 +324,7 @@ class CapabilitiesTest {
|
||||||
assertNull(capabilities.roots)
|
assertNull(capabilities.roots)
|
||||||
assertNull(capabilities.elicitation)
|
assertNull(capabilities.elicitation)
|
||||||
assertNull(capabilities.experimental)
|
assertNull(capabilities.experimental)
|
||||||
|
assertNull(capabilities.extensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -278,6 +333,11 @@ class CapabilitiesTest {
|
||||||
sampling = ClientCapabilities.sampling,
|
sampling = ClientCapabilities.sampling,
|
||||||
roots = ClientCapabilities.Roots(listChanged = false),
|
roots = ClientCapabilities.Roots(listChanged = false),
|
||||||
elicitation = ClientCapabilities.elicitation,
|
elicitation = ClientCapabilities.elicitation,
|
||||||
|
extensions = mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to buildJsonObject {
|
||||||
|
put("mimeTypes", buildJsonObject { put("text/html", true) })
|
||||||
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
verifySerializationRoundTrip(original, McpJson)
|
verifySerializationRoundTrip(original, McpJson)
|
||||||
|
|
@ -466,6 +526,32 @@ class CapabilitiesTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `should serialize ServerCapabilities with extensions`() {
|
||||||
|
val capabilities = ServerCapabilities(
|
||||||
|
extensions = mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to EmptyJsonObject,
|
||||||
|
"com.example/custom" to buildJsonObject {
|
||||||
|
put("setting", "value")
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
verifySerialization(
|
||||||
|
capabilities,
|
||||||
|
McpJson,
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"extensions": {
|
||||||
|
"io.modelcontextprotocol/ui": {},
|
||||||
|
"com.example/custom": {
|
||||||
|
"setting": "value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should serialize ServerCapabilities with experimental`() {
|
fun `should serialize ServerCapabilities with experimental`() {
|
||||||
val experimental = buildJsonObject {
|
val experimental = buildJsonObject {
|
||||||
|
|
@ -499,6 +585,9 @@ class CapabilitiesTest {
|
||||||
val experimental = buildJsonObject {
|
val experimental = buildJsonObject {
|
||||||
put("feature1", buildJsonObject { put("enabled", true) })
|
put("feature1", buildJsonObject { put("enabled", true) })
|
||||||
}
|
}
|
||||||
|
val extensions = mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to EmptyJsonObject,
|
||||||
|
)
|
||||||
val capabilities = ServerCapabilities(
|
val capabilities = ServerCapabilities(
|
||||||
tools = ServerCapabilities.Tools(listChanged = true),
|
tools = ServerCapabilities.Tools(listChanged = true),
|
||||||
resources = ServerCapabilities.Resources(
|
resources = ServerCapabilities.Resources(
|
||||||
|
|
@ -509,6 +598,7 @@ class CapabilitiesTest {
|
||||||
logging = ServerCapabilities.Logging,
|
logging = ServerCapabilities.Logging,
|
||||||
completions = ServerCapabilities.Completions,
|
completions = ServerCapabilities.Completions,
|
||||||
experimental = experimental,
|
experimental = experimental,
|
||||||
|
extensions = extensions,
|
||||||
)
|
)
|
||||||
verifySerialization(
|
verifySerialization(
|
||||||
capabilities,
|
capabilities,
|
||||||
|
|
@ -531,6 +621,9 @@ class CapabilitiesTest {
|
||||||
"feature1": {
|
"feature1": {
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"extensions": {
|
||||||
|
"io.modelcontextprotocol/ui": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
|
|
@ -578,6 +671,7 @@ class CapabilitiesTest {
|
||||||
assertNull(capabilities.logging)
|
assertNull(capabilities.logging)
|
||||||
assertNull(capabilities.completions)
|
assertNull(capabilities.completions)
|
||||||
assertNull(capabilities.experimental)
|
assertNull(capabilities.experimental)
|
||||||
|
assertNull(capabilities.extensions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -590,6 +684,9 @@ class CapabilitiesTest {
|
||||||
),
|
),
|
||||||
prompts = ServerCapabilities.Prompts(listChanged = true),
|
prompts = ServerCapabilities.Prompts(listChanged = true),
|
||||||
logging = ServerCapabilities.Logging,
|
logging = ServerCapabilities.Logging,
|
||||||
|
extensions = mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to EmptyJsonObject,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
verifySerializationRoundTrip(original, McpJson)
|
verifySerializationRoundTrip(original, McpJson)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ import io.kotest.matchers.nulls.shouldBeNull
|
||||||
import io.kotest.matchers.nulls.shouldNotBeNull
|
import io.kotest.matchers.nulls.shouldNotBeNull
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
|
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
|
||||||
import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest
|
import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.double
|
import kotlinx.serialization.json.double
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
|
|
@ -31,6 +33,7 @@ class CapabilitiesDslTest {
|
||||||
roots.shouldBeNull()
|
roots.shouldBeNull()
|
||||||
elicitation.shouldBeNull()
|
elicitation.shouldBeNull()
|
||||||
experimental.shouldBeNull()
|
experimental.shouldBeNull()
|
||||||
|
extensions.shouldBeNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,6 +59,13 @@ class CapabilitiesDslTest {
|
||||||
put("beta", false)
|
put("beta", false)
|
||||||
put("maxConcurrency", 10)
|
put("maxConcurrency", 10)
|
||||||
}
|
}
|
||||||
|
extensions(
|
||||||
|
mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to buildJsonObject {
|
||||||
|
put("mimeTypes", "text/html")
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
info("Test", "1.0")
|
info("Test", "1.0")
|
||||||
}
|
}
|
||||||
|
|
@ -80,6 +90,11 @@ class CapabilitiesDslTest {
|
||||||
get("beta")?.jsonPrimitive?.content shouldBe "false"
|
get("beta")?.jsonPrimitive?.content shouldBe "false"
|
||||||
get("maxConcurrency")?.jsonPrimitive?.content shouldBe "10"
|
get("maxConcurrency")?.jsonPrimitive?.content shouldBe "10"
|
||||||
}
|
}
|
||||||
|
extensions shouldNotBeNull {
|
||||||
|
get("io.modelcontextprotocol/ui") shouldNotBeNull {
|
||||||
|
get("mimeTypes")?.jsonPrimitive?.content shouldBe "text/html"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,6 +125,50 @@ class CapabilitiesDslTest {
|
||||||
requestNull.params.capabilities.roots?.listChanged.shouldBeNull()
|
requestNull.params.capabilities.roots?.listChanged.shouldBeNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `capabilities should build with extensions containing empty settings`() {
|
||||||
|
val request = buildInitializeRequest {
|
||||||
|
protocolVersion = "2024-11-05"
|
||||||
|
capabilities {
|
||||||
|
extensions(
|
||||||
|
mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to EmptyJsonObject,
|
||||||
|
"com.example/custom" to EmptyJsonObject,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
info("Test", "1.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
request.params.capabilities.shouldNotBeNull {
|
||||||
|
extensions shouldNotBeNull {
|
||||||
|
size shouldBe 2
|
||||||
|
get("io.modelcontextprotocol/ui") shouldBe EmptyJsonObject
|
||||||
|
get("com.example/custom") shouldBe EmptyJsonObject
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `capabilities should overwrite extensions when set multiple times`() {
|
||||||
|
val request = buildInitializeRequest {
|
||||||
|
protocolVersion = "2024-11-05"
|
||||||
|
capabilities {
|
||||||
|
extensions(mapOf("io.modelcontextprotocol/ui" to EmptyJsonObject))
|
||||||
|
extensions(mapOf("com.example/custom" to EmptyJsonObject)) // Should overwrite
|
||||||
|
}
|
||||||
|
info("Test", "1.0")
|
||||||
|
}
|
||||||
|
|
||||||
|
request.params.capabilities.shouldNotBeNull {
|
||||||
|
extensions shouldNotBeNull {
|
||||||
|
size shouldBe 1
|
||||||
|
get("com.example/custom") shouldBe EmptyJsonObject
|
||||||
|
get("io.modelcontextprotocol/ui").shouldBeNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `capabilities should overwrite when same field set multiple times`() {
|
fun `capabilities should overwrite when same field set multiple times`() {
|
||||||
val request = buildInitializeRequest {
|
val request = buildInitializeRequest {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import io.kotest.matchers.nulls.shouldNotBeNull
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
|
import io.modelcontextprotocol.kotlin.sdk.ExperimentalMcpApi
|
||||||
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
|
import io.modelcontextprotocol.kotlin.sdk.types.ClientCapabilities
|
||||||
|
import io.modelcontextprotocol.kotlin.sdk.types.EmptyJsonObject
|
||||||
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
|
import io.modelcontextprotocol.kotlin.sdk.types.Implementation
|
||||||
import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest
|
import io.modelcontextprotocol.kotlin.sdk.types.buildInitializeRequest
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
|
@ -30,6 +31,11 @@ class InitializeDslTest {
|
||||||
experimental {
|
experimental {
|
||||||
put("custom", true)
|
put("custom", true)
|
||||||
}
|
}
|
||||||
|
extensions(
|
||||||
|
mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to EmptyJsonObject,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
info(
|
info(
|
||||||
name = "TestClient",
|
name = "TestClient",
|
||||||
|
|
@ -45,6 +51,9 @@ class InitializeDslTest {
|
||||||
roots?.listChanged shouldBe true
|
roots?.listChanged shouldBe true
|
||||||
elicitation?.get("mode")?.jsonPrimitive?.content shouldBe "interactive"
|
elicitation?.get("mode")?.jsonPrimitive?.content shouldBe "interactive"
|
||||||
experimental?.get("custom")?.jsonPrimitive?.content shouldBe "true"
|
experimental?.get("custom")?.jsonPrimitive?.content shouldBe "true"
|
||||||
|
extensions shouldNotBeNull {
|
||||||
|
get("io.modelcontextprotocol/ui") shouldBe EmptyJsonObject
|
||||||
|
}
|
||||||
}
|
}
|
||||||
request.params.clientInfo.shouldNotBeNull {
|
request.params.clientInfo.shouldNotBeNull {
|
||||||
name shouldBe "TestClient"
|
name shouldBe "TestClient"
|
||||||
|
|
@ -74,6 +83,9 @@ class InitializeDslTest {
|
||||||
val samplingObj = buildJsonObject { put("key", "value") }
|
val samplingObj = buildJsonObject { put("key", "value") }
|
||||||
val elicitationObj = buildJsonObject { put("key", "value") }
|
val elicitationObj = buildJsonObject { put("key", "value") }
|
||||||
val experimentalObj = buildJsonObject { put("key", "value") }
|
val experimentalObj = buildJsonObject { put("key", "value") }
|
||||||
|
val extensionsMap = mapOf(
|
||||||
|
"io.modelcontextprotocol/ui" to buildJsonObject { put("key", "value") },
|
||||||
|
)
|
||||||
|
|
||||||
val request = buildInitializeRequest {
|
val request = buildInitializeRequest {
|
||||||
protocolVersion = "1.0"
|
protocolVersion = "1.0"
|
||||||
|
|
@ -81,6 +93,7 @@ class InitializeDslTest {
|
||||||
sampling(samplingObj)
|
sampling(samplingObj)
|
||||||
elicitation(elicitationObj)
|
elicitation(elicitationObj)
|
||||||
experimental(experimentalObj)
|
experimental(experimentalObj)
|
||||||
|
extensions(extensionsMap)
|
||||||
}
|
}
|
||||||
info("Test", "1.0")
|
info("Test", "1.0")
|
||||||
}
|
}
|
||||||
|
|
@ -89,6 +102,7 @@ class InitializeDslTest {
|
||||||
sampling shouldBe samplingObj
|
sampling shouldBe samplingObj
|
||||||
elicitation shouldBe elicitationObj
|
elicitation shouldBe elicitationObj
|
||||||
experimental shouldBe experimentalObj
|
experimental shouldBe experimentalObj
|
||||||
|
extensions shouldBe extensionsMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue