Skip to content

Commit bf8dc6d

Browse files
eritscherdevcrocod
andauthored
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 ](https://github.com/modelcontextprotocol/go-sdk/blob/862d78a265cb1f1b9692ff49cfb8bfd109b6ebc9/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>
1 parent f6ad332 commit bf8dc6d

6 files changed

Lines changed: 217 additions & 8 deletions

File tree

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)