Skip to content

Commit b6eaeb6

Browse files
eritscherkpavlov
authored andcommitted
feat(core): add extensions field to ClientCapabilities and ServerCapabilities
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.
1 parent f02f5d3 commit b6eaeb6

File tree

6 files changed

+215
-8
lines changed

6 files changed

+215
-8
lines changed

kotlin-sdk-core/api/kotlin-sdk-core.api

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -606,17 +606,19 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/CancelledNotificatio
606606
public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities {
607607
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Companion;
608608
public fun <init> ()V
609-
public fun <init> (Lkotlinx/serialization/json/JsonObject;Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;Lkotlinx/serialization/json/JsonObject;Lkotlinx/serialization/json/JsonObject;)V
610-
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
609+
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
610+
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
611611
public final fun component1 ()Lkotlinx/serialization/json/JsonObject;
612612
public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;
613613
public final fun component3 ()Lkotlinx/serialization/json/JsonObject;
614614
public final fun component4 ()Lkotlinx/serialization/json/JsonObject;
615-
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;
616-
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;
615+
public final fun component5 ()Ljava/util/Map;
616+
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;
617+
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;
617618
public fun equals (Ljava/lang/Object;)Z
618619
public final fun getElicitation ()Lkotlinx/serialization/json/JsonObject;
619620
public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject;
621+
public final fun getExtensions ()Ljava/util/Map;
620622
public final fun getRoots ()Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilities$Roots;
621623
public final fun getSampling ()Lkotlinx/serialization/json/JsonObject;
622624
public fun hashCode ()I
@@ -676,6 +678,7 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBu
676678
public final fun elicitation (Lkotlinx/serialization/json/JsonObject;)V
677679
public final fun experimental (Lkotlin/jvm/functions/Function1;)V
678680
public final fun experimental (Lkotlinx/serialization/json/JsonObject;)V
681+
public final fun extensions (Ljava/util/Map;)V
679682
public final fun roots (Ljava/lang/Boolean;)V
680683
public static synthetic fun roots$default (Lio/modelcontextprotocol/kotlin/sdk/types/ClientCapabilitiesBuilder;Ljava/lang/Boolean;ILjava/lang/Object;)V
681684
public final fun sampling (Lkotlin/jvm/functions/Function1;)V
@@ -4245,19 +4248,21 @@ public final class io/modelcontextprotocol/kotlin/sdk/types/Sampling_dslKt {
42454248
public final class io/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities {
42464249
public static final field Companion Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Companion;
42474250
public fun <init> ()V
4248-
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
4249-
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
4251+
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
4252+
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
42504253
public final fun component1 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Tools;
42514254
public final fun component2 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;
42524255
public final fun component3 ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;
42534256
public final fun component4 ()Lkotlinx/serialization/json/JsonObject;
42544257
public final fun component5 ()Lkotlinx/serialization/json/JsonObject;
42554258
public final fun component6 ()Lkotlinx/serialization/json/JsonObject;
4256-
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;
4257-
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;
4259+
public final fun component7 ()Ljava/util/Map;
4260+
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;
4261+
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;
42584262
public fun equals (Ljava/lang/Object;)Z
42594263
public final fun getCompletions ()Lkotlinx/serialization/json/JsonObject;
42604264
public final fun getExperimental ()Lkotlinx/serialization/json/JsonObject;
4265+
public final fun getExtensions ()Ljava/util/Map;
42614266
public final fun getLogging ()Lkotlinx/serialization/json/JsonObject;
42624267
public final fun getPrompts ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Prompts;
42634268
public final fun getResources ()Lio/modelcontextprotocol/kotlin/sdk/types/ServerCapabilities$Resources;

kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.dsl.kt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() {
3939
private var sampling: JsonObject? = null
4040
private var roots: ClientCapabilities.Roots? = null
4141
private var elicitation: JsonObject? = null
42+
private var extensions: Map<String, JsonObject>? = null
4243
private var experimental: JsonObject? = null
4344

4445
/**
@@ -132,6 +133,30 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() {
132133
*/
133134
public fun elicitation(block: JsonObjectBuilder.() -> Unit): Unit = elicitation(buildJsonObject(block))
134135

136+
/**
137+
* Defines extensions that the client supports.
138+
*
139+
* Extension identifiers use the format `{vendor-prefix}/{extension-name}`,
140+
* e.g., `"io.modelcontextprotocol/ui"`. Each value is an extension-specific
141+
* settings object; an empty [JsonObject] indicates no settings.
142+
*
143+
* Example:
144+
* ```kotlin
145+
* capabilities {
146+
* extensions(mapOf(
147+
* "io.modelcontextprotocol/ui" to buildJsonObject {
148+
* put("mimeTypes", JsonArray(listOf(JsonPrimitive("text/html"))))
149+
* }
150+
* ))
151+
* }
152+
* ```
153+
*
154+
* @param value The map of extension identifiers to their settings
155+
*/
156+
public fun extensions(value: Map<String, JsonObject>) {
157+
this.extensions = value
158+
}
159+
135160
/**
136161
* Defines experimental, non-standard capabilities that the client supports.
137162
*
@@ -177,6 +202,7 @@ public class ClientCapabilitiesBuilder @PublishedApi internal constructor() {
177202
sampling = sampling,
178203
roots = roots,
179204
elicitation = elicitation,
205+
extensions = extensions,
180206
experimental = experimental,
181207
)
182208
}

kotlin-sdk-core/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/types/capabilities.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,17 @@ public data class Implementation(
4545
* @property elicitation Present if the client supports elicitation from the server.
4646
* @property experimental Experimental, non-standard capabilities that the client supports.
4747
* Keys are capability names, values are capability-specific configuration objects.
48+
* @property extensions Optional extensions that the client supports.
49+
* Keys are extension identifiers (e.g., `"io.modelcontextprotocol/ui"`),
50+
* values are extension-specific settings objects.
4851
*/
4952
@Serializable
5053
public data class ClientCapabilities(
5154
public val sampling: JsonObject? = null,
5255
public val roots: Roots? = null,
5356
public val elicitation: JsonObject? = null,
5457
public val experimental: JsonObject? = null,
58+
public val extensions: Map<String, JsonObject>? = null,
5559
) {
5660

5761
/**
@@ -93,6 +97,9 @@ public data class ClientCapabilities(
9397
* @property completions Present if the server supports argument autocompletion suggestions.
9498
* Keys are capability names, values are capability-specific configuration objects.
9599
* @property experimental Experimental, non-standard capabilities that the server supports.
100+
* @property extensions Optional extensions that the server supports.
101+
* Keys are extension identifiers (e.g., `"io.modelcontextprotocol/ui"`),
102+
* values are extension-specific settings objects.
96103
*/
97104
@Serializable
98105
public data class ServerCapabilities(
@@ -102,6 +109,7 @@ public data class ServerCapabilities(
102109
val logging: JsonObject? = null,
103110
val completions: JsonObject? = null,
104111
val experimental: JsonObject? = null,
112+
val extensions: Map<String, JsonObject>? = null,
105113
) {
106114

107115
/**

kotlin-sdk-core/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/types/CapabilitiesTest.kt

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,53 @@ class CapabilitiesTest {
182182
)
183183
}
184184

185+
@Test
186+
fun `should serialize ClientCapabilities with extensions`() {
187+
val extensions = mapOf(
188+
"io.modelcontextprotocol/ui" to buildJsonObject {
189+
put("mimeTypes", buildJsonObject { put("text/html", true) })
190+
},
191+
)
192+
val capabilities = ClientCapabilities(
193+
extensions = extensions,
194+
)
195+
verifySerialization(
196+
capabilities,
197+
McpJson,
198+
"""
199+
{
200+
"extensions": {
201+
"io.modelcontextprotocol/ui": {
202+
"mimeTypes": {
203+
"text/html": true
204+
}
205+
}
206+
}
207+
}
208+
""".trimIndent(),
209+
)
210+
}
211+
212+
@Test
213+
fun `should serialize ClientCapabilities with empty extensions settings`() {
214+
val capabilities = ClientCapabilities(
215+
extensions = mapOf(
216+
"io.modelcontextprotocol/ui" to EmptyJsonObject,
217+
),
218+
)
219+
verifySerialization(
220+
capabilities,
221+
McpJson,
222+
"""
223+
{
224+
"extensions": {
225+
"io.modelcontextprotocol/ui": {}
226+
}
227+
}
228+
""".trimIndent(),
229+
)
230+
}
231+
185232
@Test
186233
fun `should serialize ClientCapabilities with experimental`() {
187234
val experimental = buildJsonObject {
@@ -215,11 +262,15 @@ class CapabilitiesTest {
215262
val experimental = buildJsonObject {
216263
put("feature1", buildJsonObject { put("enabled", true) })
217264
}
265+
val extensions = mapOf(
266+
"io.modelcontextprotocol/ui" to EmptyJsonObject,
267+
)
218268
val capabilities = ClientCapabilities(
219269
sampling = ClientCapabilities.sampling,
220270
roots = ClientCapabilities.Roots(listChanged = true),
221271
elicitation = ClientCapabilities.elicitation,
222272
experimental = experimental,
273+
extensions = extensions,
223274
)
224275
verifySerialization(
225276
capabilities,
@@ -235,6 +286,9 @@ class CapabilitiesTest {
235286
"feature1": {
236287
"enabled": true
237288
}
289+
},
290+
"extensions": {
291+
"io.modelcontextprotocol/ui": {}
238292
}
239293
}
240294
""".trimIndent(),
@@ -270,6 +324,7 @@ class CapabilitiesTest {
270324
assertNull(capabilities.roots)
271325
assertNull(capabilities.elicitation)
272326
assertNull(capabilities.experimental)
327+
assertNull(capabilities.extensions)
273328
}
274329

275330
@Test
@@ -278,6 +333,11 @@ class CapabilitiesTest {
278333
sampling = ClientCapabilities.sampling,
279334
roots = ClientCapabilities.Roots(listChanged = false),
280335
elicitation = ClientCapabilities.elicitation,
336+
extensions = mapOf(
337+
"io.modelcontextprotocol/ui" to buildJsonObject {
338+
put("mimeTypes", buildJsonObject { put("text/html", true) })
339+
},
340+
),
281341
)
282342

283343
verifySerializationRoundTrip(original, McpJson)
@@ -466,6 +526,32 @@ class CapabilitiesTest {
466526
)
467527
}
468528

529+
@Test
530+
fun `should serialize ServerCapabilities with extensions`() {
531+
val capabilities = ServerCapabilities(
532+
extensions = mapOf(
533+
"io.modelcontextprotocol/ui" to EmptyJsonObject,
534+
"com.example/custom" to buildJsonObject {
535+
put("setting", "value")
536+
},
537+
),
538+
)
539+
verifySerialization(
540+
capabilities,
541+
McpJson,
542+
"""
543+
{
544+
"extensions": {
545+
"io.modelcontextprotocol/ui": {},
546+
"com.example/custom": {
547+
"setting": "value"
548+
}
549+
}
550+
}
551+
""".trimIndent(),
552+
)
553+
}
554+
469555
@Test
470556
fun `should serialize ServerCapabilities with experimental`() {
471557
val experimental = buildJsonObject {
@@ -499,6 +585,9 @@ class CapabilitiesTest {
499585
val experimental = buildJsonObject {
500586
put("feature1", buildJsonObject { put("enabled", true) })
501587
}
588+
val extensions = mapOf(
589+
"io.modelcontextprotocol/ui" to EmptyJsonObject,
590+
)
502591
val capabilities = ServerCapabilities(
503592
tools = ServerCapabilities.Tools(listChanged = true),
504593
resources = ServerCapabilities.Resources(
@@ -509,6 +598,7 @@ class CapabilitiesTest {
509598
logging = ServerCapabilities.Logging,
510599
completions = ServerCapabilities.Completions,
511600
experimental = experimental,
601+
extensions = extensions,
512602
)
513603
verifySerialization(
514604
capabilities,
@@ -531,6 +621,9 @@ class CapabilitiesTest {
531621
"feature1": {
532622
"enabled": true
533623
}
624+
},
625+
"extensions": {
626+
"io.modelcontextprotocol/ui": {}
534627
}
535628
}
536629
""".trimIndent(),
@@ -578,6 +671,7 @@ class CapabilitiesTest {
578671
assertNull(capabilities.logging)
579672
assertNull(capabilities.completions)
580673
assertNull(capabilities.experimental)
674+
assertNull(capabilities.extensions)
581675
}
582676

583677
@Test
@@ -590,6 +684,9 @@ class CapabilitiesTest {
590684
),
591685
prompts = ServerCapabilities.Prompts(listChanged = true),
592686
logging = ServerCapabilities.Logging,
687+
extensions = mapOf(
688+
"io.modelcontextprotocol/ui" to EmptyJsonObject,
689+
),
593690
)
594691

595692
verifySerializationRoundTrip(original, McpJson)

0 commit comments

Comments
 (0)