Visitar URL original
SEP-2133: add extensions attribute to ClientCapabilities and ServerCa… · modelcontextprotocol/java-sdk@73a9a8f · GitHub
Skip to content

Commit 73a9a8f

Browse files
YousefHaggyYugi-2Kehrlann
authored
SEP-2133: add extensions attribute to ClientCapabilities and ServerCapabilities (#1064)
Add the optional `extensions` field to `ClientCapabilities` and `ServerCapabilities`. Implements [SEP-2133](https://modelcontextprotocol.io/seps/2133-extensions). Co-authored-by: Yugi-2 <admin@getgeomind.com> Co-authored-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent 1cf7903 commit 73a9a8f

2 files changed

Lines changed: 109 additions & 4 deletions

File tree

‎mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java‎

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -577,14 +577,23 @@ public InitializeResult build() {
577577
* @param roots Present if the client supports listing roots
578578
* @param sampling Present if the client supports sampling from an LLM
579579
* @param elicitation Present if the client supports elicitation from the server
580+
* @param extensions Present if the client supports MCP extensions, keyed by extension
581+
* identifier
580582
*/
581583
@JsonInclude(JsonInclude.Include.NON_ABSENT)
582584
@JsonIgnoreProperties(ignoreUnknown = true)
583585
public record ClientCapabilities( // @formatter:off
584586
@JsonProperty("experimental") Map<String, Object> experimental,
585587
@JsonProperty("roots") RootCapabilities roots,
586588
@JsonProperty("sampling") Sampling sampling,
587-
@JsonProperty("elicitation") Elicitation elicitation) { // @formatter:on
589+
@JsonProperty("elicitation") Elicitation elicitation,
590+
@JsonProperty("extensions") Map<String, Map<String, Object>> extensions) { // @formatter:on
591+
592+
// Keep the old constructor so existing callers still compile
593+
public ClientCapabilities(Map<String, Object> experimental, RootCapabilities roots, Sampling sampling,
594+
Elicitation elicitation) {
595+
this(experimental, roots, sampling, elicitation, null);
596+
}
588597

589598
/**
590599
* Present if the client supports listing roots.
@@ -723,6 +732,8 @@ public static class Builder {
723732

724733
private Elicitation elicitation;
725734

735+
private Map<String, Map<String, Object>> extensions;
736+
726737
public Builder experimental(Map<String, Object> experimental) {
727738
this.experimental = experimental;
728739
return this;
@@ -765,8 +776,13 @@ public Builder elicitation(Elicitation elicitation) {
765776
return this;
766777
}
767778

779+
public Builder extensions(Map<String, Map<String, Object>> extensions) {
780+
this.extensions = extensions;
781+
return this;
782+
}
783+
768784
public ClientCapabilities build() {
769-
return new ClientCapabilities(experimental, roots, sampling, elicitation);
785+
return new ClientCapabilities(experimental, roots, sampling, elicitation, extensions);
770786
}
771787

772788
}
@@ -785,6 +801,8 @@ public ClientCapabilities build() {
785801
* @param prompts Present if the server offers any prompt templates
786802
* @param resources Present if the server offers any resources to read
787803
* @param tools Present if the server offers any tools to call
804+
* @param extensions Present if the server supports MCP extensions, keyed by extension
805+
* identifier
788806
*/
789807
@JsonInclude(JsonInclude.Include.NON_ABSENT)
790808
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -794,7 +812,15 @@ public record ServerCapabilities( // @formatter:off
794812
@JsonProperty("logging") LoggingCapabilities logging,
795813
@JsonProperty("prompts") PromptCapabilities prompts,
796814
@JsonProperty("resources") ResourceCapabilities resources,
797-
@JsonProperty("tools") ToolCapabilities tools) { // @formatter:on
815+
@JsonProperty("tools") ToolCapabilities tools,
816+
@JsonProperty("extensions") Map<String, Map<String, Object>> extensions) { // @formatter:on
817+
818+
// Keep the old constructor so existing callers still compile
819+
public ServerCapabilities(CompletionCapabilities completions, Map<String, Object> experimental,
820+
LoggingCapabilities logging, PromptCapabilities prompts, ResourceCapabilities resources,
821+
ToolCapabilities tools) {
822+
this(completions, experimental, logging, prompts, resources, tools, null);
823+
}
798824

799825
/**
800826
* Present if the server supports argument autocompletion suggestions.
@@ -923,6 +949,7 @@ public Builder mutate() {
923949
builder.prompts = this.prompts;
924950
builder.resources = this.resources;
925951
builder.tools = this.tools;
952+
builder.extensions = this.extensions;
926953
return builder;
927954
}
928955

@@ -944,6 +971,8 @@ public static class Builder {
944971

945972
private ToolCapabilities tools;
946973

974+
private Map<String, Map<String, Object>> extensions;
975+
947976
public Builder completions() {
948977
this.completions = new CompletionCapabilities();
949978
return this;
@@ -974,8 +1003,14 @@ public Builder tools(Boolean listChanged) {
9741003
return this;
9751004
}
9761005

1006+
public Builder extensions(Map<String, Map<String, Object>> extensions) {
1007+
this.extensions = extensions;
1008+
return this;
1009+
}
1010+
9771011
public ServerCapabilities build() {
978-
return new ServerCapabilities(completions, experimental, logging, prompts, resources, tools);
1012+
return new ServerCapabilities(completions, experimental, logging, prompts, resources, tools,
1013+
extensions);
9791014
}
9801015

9811016
}

‎mcp-test/src/test/java/io/modelcontextprotocol/spec/SchemaEvolutionTests.java‎

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import java.io.IOException;
1111
import java.util.List;
12+
import java.util.Map;
1213

1314
import io.modelcontextprotocol.json.McpJsonMapper;
1415
import org.junit.jupiter.api.Test;
@@ -143,6 +144,75 @@ void serverCapabilitiesUnknownFieldsIgnored() throws IOException {
143144
assertThat(caps.tools().listChanged()).isTrue();
144145
}
145146

147+
@Test
148+
void serverCapabilitiesWithoutExtensionsDeserializesAsNull() throws IOException {
149+
String json = """
150+
{"tools":{"listChanged":true}}
151+
""";
152+
McpSchema.ServerCapabilities caps = mapper.readValue(json, McpSchema.ServerCapabilities.class);
153+
assertThat(caps.extensions()).isNull();
154+
}
155+
156+
@Test
157+
void serverCapabilitiesWithNullExtensionsOmitsFieldOnWire() throws IOException {
158+
String json = mapper.writeValueAsString(McpSchema.ServerCapabilities.builder().tools(true).build());
159+
assertThat(json).doesNotContain("extensions");
160+
}
161+
162+
@Test
163+
void serverCapabilitiesExtensionsRoundTrip() throws IOException {
164+
Map<String, Map<String, Object>> extensions = Map.of("com.example/ext-with-settings", Map.of("maxDepth", 3),
165+
"com.example/ext-without-settings", Map.of());
166+
McpSchema.ServerCapabilities caps = McpSchema.ServerCapabilities.builder().extensions(extensions).build();
167+
168+
String json = mapper.writeValueAsString(caps);
169+
assertThat(json).contains("\"com.example/ext-without-settings\":{}");
170+
171+
McpSchema.ServerCapabilities parsed = mapper.readValue(json, McpSchema.ServerCapabilities.class);
172+
assertThat(parsed.extensions()).isEqualTo(extensions);
173+
}
174+
175+
// -----------------------------------------------------------------------
176+
// ClientCapabilities.extensions
177+
// -----------------------------------------------------------------------
178+
179+
@Test
180+
void clientCapabilitiesWithoutExtensionsDeserializesAsNull() throws IOException {
181+
String json = """
182+
{"roots":{"listChanged":true},"sampling":{}}
183+
""";
184+
McpSchema.ClientCapabilities caps = mapper.readValue(json, McpSchema.ClientCapabilities.class);
185+
assertThat(caps.extensions()).isNull();
186+
}
187+
188+
@Test
189+
void clientCapabilitiesWithNullExtensionsOmitsFieldOnWire() throws IOException {
190+
String json = mapper.writeValueAsString(McpSchema.ClientCapabilities.builder().sampling().build());
191+
assertThat(json).doesNotContain("extensions");
192+
}
193+
194+
@Test
195+
void clientCapabilitiesWithExtensionsUnknownFieldsIgnored() throws IOException {
196+
String json = """
197+
{"extensions":{"io.modelcontextprotocol/tasks":{}},"futureCapability":{"a":1}}
198+
""";
199+
McpSchema.ClientCapabilities caps = mapper.readValue(json, McpSchema.ClientCapabilities.class);
200+
assertThat(caps.extensions()).containsOnlyKeys("io.modelcontextprotocol/tasks");
201+
}
202+
203+
@Test
204+
void clientCapabilitiesExtensionsRoundTrip() throws IOException {
205+
Map<String, Map<String, Object>> extensions = Map.of("com.example/ext-with-settings", Map.of("maxDepth", 3),
206+
"com.example/ext-without-settings", Map.of());
207+
McpSchema.ClientCapabilities caps = McpSchema.ClientCapabilities.builder().extensions(extensions).build();
208+
209+
String json = mapper.writeValueAsString(caps);
210+
assertThat(json).contains("\"com.example/ext-without-settings\":{}");
211+
212+
McpSchema.ClientCapabilities parsed = mapper.readValue(json, McpSchema.ClientCapabilities.class);
213+
assertThat(parsed.extensions()).isEqualTo(extensions);
214+
}
215+
146216
// -----------------------------------------------------------------------
147217
// JSONRPCError
148218
// -----------------------------------------------------------------------

0 commit comments

Comments
 (0)