Skip to content

Commit 37a9f3a

Browse files
Add @JsonIgnoreProperties(ignoreUnknown = true) to capability sub-records
The top-level ClientCapabilities and ServerCapabilities records have @JsonIgnoreProperties(ignoreUnknown = true) but their nested sub-records do not. Since ObjectMapper defaults to FAIL_ON_UNKNOWN_PROPERTIES = true, unknown fields on sub-objects cause deserialization failures. This already caused a real breakage when elicitation gained form/url fields (#724). The MCP spec explicitly does not close capability objects (additionalProperties is never set to false), so SDKs must tolerate unknown fields for forward compatibility. Add the annotation to: - ClientCapabilities.Sampling - ClientCapabilities.Elicitation, Elicitation.Form, Elicitation.Url - ServerCapabilities.CompletionCapabilities - ServerCapabilities.LoggingCapabilities - ServerCapabilities.PromptCapabilities - ServerCapabilities.ResourceCapabilities - ServerCapabilities.ToolCapabilities Fixes #766 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 26304a7 commit 37a9f3a

2 files changed

Lines changed: 80 additions & 0 deletions

File tree

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ public record RootCapabilities(@JsonProperty("listChanged") Boolean listChanged)
404404
* from MCP servers in their prompts.
405405
*/
406406
@JsonInclude(JsonInclude.Include.NON_ABSENT)
407+
@JsonIgnoreProperties(ignoreUnknown = true)
407408
public record Sampling() {
408409
}
409410

@@ -431,19 +432,22 @@ public record Sampling() {
431432
* @param url support for out-of-band URL-based elicitation
432433
*/
433434
@JsonInclude(JsonInclude.Include.NON_ABSENT)
435+
@JsonIgnoreProperties(ignoreUnknown = true)
434436
public record Elicitation(@JsonProperty("form") Form form, @JsonProperty("url") Url url) {
435437

436438
/**
437439
* Marker record indicating support for form-based elicitation mode.
438440
*/
439441
@JsonInclude(JsonInclude.Include.NON_ABSENT)
442+
@JsonIgnoreProperties(ignoreUnknown = true)
440443
public record Form() {
441444
}
442445

443446
/**
444447
* Marker record indicating support for URL-based elicitation mode.
445448
*/
446449
@JsonInclude(JsonInclude.Include.NON_ABSENT)
450+
@JsonIgnoreProperties(ignoreUnknown = true)
447451
public record Url() {
448452
}
449453

@@ -542,13 +546,15 @@ public record ServerCapabilities( // @formatter:off
542546
* Present if the server supports argument autocompletion suggestions.
543547
*/
544548
@JsonInclude(JsonInclude.Include.NON_ABSENT)
549+
@JsonIgnoreProperties(ignoreUnknown = true)
545550
public record CompletionCapabilities() {
546551
}
547552

548553
/**
549554
* Present if the server supports sending log messages to the client.
550555
*/
551556
@JsonInclude(JsonInclude.Include.NON_ABSENT)
557+
@JsonIgnoreProperties(ignoreUnknown = true)
552558
public record LoggingCapabilities() {
553559
}
554560

@@ -559,6 +565,7 @@ public record LoggingCapabilities() {
559565
* the prompt list
560566
*/
561567
@JsonInclude(JsonInclude.Include.NON_ABSENT)
568+
@JsonIgnoreProperties(ignoreUnknown = true)
562569
public record PromptCapabilities(@JsonProperty("listChanged") Boolean listChanged) {
563570
}
564571

@@ -570,6 +577,7 @@ public record PromptCapabilities(@JsonProperty("listChanged") Boolean listChange
570577
* the resource list
571578
*/
572579
@JsonInclude(JsonInclude.Include.NON_ABSENT)
580+
@JsonIgnoreProperties(ignoreUnknown = true)
573581
public record ResourceCapabilities(@JsonProperty("subscribe") Boolean subscribe,
574582
@JsonProperty("listChanged") Boolean listChanged) {
575583
}
@@ -581,6 +589,7 @@ public record ResourceCapabilities(@JsonProperty("subscribe") Boolean subscribe,
581589
* the tool list
582590
*/
583591
@JsonInclude(JsonInclude.Include.NON_ABSENT)
592+
@JsonIgnoreProperties(ignoreUnknown = true)
584593
public record ToolCapabilities(@JsonProperty("listChanged") Boolean listChanged) {
585594
}
586595

mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1760,4 +1760,75 @@ void testProgressNotificationWithoutMessage() throws Exception {
17601760
{"progressToken":"progress-token-789","progress":0.25}"""));
17611761
}
17621762

1763+
// Capability sub-records: unknown fields should be silently ignored (#766)
1764+
1765+
@Test
1766+
void testSamplingIgnoresUnknownFields() throws Exception {
1767+
McpSchema.ClientCapabilities.Sampling sampling = JSON_MAPPER.readValue("""
1768+
{"futureField": true}""", McpSchema.ClientCapabilities.Sampling.class);
1769+
assertThat(sampling).isNotNull();
1770+
}
1771+
1772+
@Test
1773+
void testElicitationIgnoresUnknownFields() throws Exception {
1774+
McpSchema.ClientCapabilities.Elicitation elicitation = JSON_MAPPER.readValue("""
1775+
{"form": {}, "url": {}, "futureField": "value"}""", McpSchema.ClientCapabilities.Elicitation.class);
1776+
assertThat(elicitation).isNotNull();
1777+
}
1778+
1779+
@Test
1780+
void testElicitationFormIgnoresUnknownFields() throws Exception {
1781+
McpSchema.ClientCapabilities.Elicitation.Form form = JSON_MAPPER.readValue("""
1782+
{"futureField": 42}""", McpSchema.ClientCapabilities.Elicitation.Form.class);
1783+
assertThat(form).isNotNull();
1784+
}
1785+
1786+
@Test
1787+
void testElicitationUrlIgnoresUnknownFields() throws Exception {
1788+
McpSchema.ClientCapabilities.Elicitation.Url url = JSON_MAPPER.readValue("""
1789+
{"futureField": "value"}""", McpSchema.ClientCapabilities.Elicitation.Url.class);
1790+
assertThat(url).isNotNull();
1791+
}
1792+
1793+
@Test
1794+
void testCompletionCapabilitiesIgnoresUnknownFields() throws Exception {
1795+
McpSchema.ServerCapabilities.CompletionCapabilities completions = JSON_MAPPER.readValue("""
1796+
{"futureField": true}""", McpSchema.ServerCapabilities.CompletionCapabilities.class);
1797+
assertThat(completions).isNotNull();
1798+
}
1799+
1800+
@Test
1801+
void testLoggingCapabilitiesIgnoresUnknownFields() throws Exception {
1802+
McpSchema.ServerCapabilities.LoggingCapabilities logging = JSON_MAPPER.readValue("""
1803+
{"futureField": "value"}""", McpSchema.ServerCapabilities.LoggingCapabilities.class);
1804+
assertThat(logging).isNotNull();
1805+
}
1806+
1807+
@Test
1808+
void testPromptCapabilitiesIgnoresUnknownFields() throws Exception {
1809+
McpSchema.ServerCapabilities.PromptCapabilities prompts = JSON_MAPPER.readValue("""
1810+
{"listChanged": true, "futureField": "value"}""",
1811+
McpSchema.ServerCapabilities.PromptCapabilities.class);
1812+
assertThat(prompts).isNotNull();
1813+
assertThat(prompts.listChanged()).isTrue();
1814+
}
1815+
1816+
@Test
1817+
void testResourceCapabilitiesIgnoresUnknownFields() throws Exception {
1818+
McpSchema.ServerCapabilities.ResourceCapabilities resources = JSON_MAPPER.readValue("""
1819+
{"subscribe": true, "listChanged": false, "futureField": 123}""",
1820+
McpSchema.ServerCapabilities.ResourceCapabilities.class);
1821+
assertThat(resources).isNotNull();
1822+
assertThat(resources.subscribe()).isTrue();
1823+
assertThat(resources.listChanged()).isFalse();
1824+
}
1825+
1826+
@Test
1827+
void testToolCapabilitiesIgnoresUnknownFields() throws Exception {
1828+
McpSchema.ServerCapabilities.ToolCapabilities tools = JSON_MAPPER.readValue("""
1829+
{"listChanged": true, "futureField": "value"}""", McpSchema.ServerCapabilities.ToolCapabilities.class);
1830+
assertThat(tools).isNotNull();
1831+
assertThat(tools.listChanged()).isTrue();
1832+
}
1833+
17631834
}

0 commit comments

Comments
 (0)