+ * Per the 2025-11-25 spec, clients can declare support for specific elicitation + * modes: + *
+ * For backward compatibility, an empty elicitation object {@code {}} is
+ * equivalent to declaring support for form mode only.
+ *
+ * @param form support for in-band form-based elicitation
+ * @param url support for out-of-band URL-based elicitation
*/
@JsonInclude(JsonInclude.Include.NON_ABSENT)
- public record Elicitation() {
+ public record Elicitation(@JsonProperty("form") Form form, @JsonProperty("url") Url url) {
+
+ /**
+ * Marker record indicating support for form-based elicitation mode.
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ public record Form() {
+ }
+
+ /**
+ * Marker record indicating support for URL-based elicitation mode.
+ */
+ @JsonInclude(JsonInclude.Include.NON_ABSENT)
+ public record Url() {
+ }
+
+ /**
+ * Creates an Elicitation with default settings (backward compatible, produces
+ * empty JSON object).
+ */
+ public Elicitation() {
+ this(null, null);
+ }
}
public static Builder builder() {
@@ -450,11 +488,28 @@ public Builder sampling() {
return this;
}
+ /**
+ * Enables elicitation capability with default settings (backward compatible,
+ * produces empty JSON object).
+ * @return this builder
+ */
public Builder elicitation() {
this.elicitation = new Elicitation();
return this;
}
+ /**
+ * Enables elicitation capability with explicit form and/or url mode support.
+ * @param form whether to support form-based elicitation
+ * @param url whether to support URL-based elicitation
+ * @return this builder
+ */
+ public Builder elicitation(boolean form, boolean url) {
+ this.elicitation = new Elicitation(form ? new Elicitation.Form() : null,
+ url ? new Elicitation.Url() : null);
+ return this;
+ }
+
public ClientCapabilities build() {
return new ClientCapabilities(experimental, roots, sampling, elicitation);
}
diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/ProtocolVersions.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/ProtocolVersions.java
index d8cb913a5..d3d34db62 100644
--- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/ProtocolVersions.java
+++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/ProtocolVersions.java
@@ -20,4 +20,10 @@ public interface ProtocolVersions {
*/
String MCP_2025_06_18 = "2025-06-18";
+ /**
+ * MCP protocol version for 2025-11-25.
+ * https://modelcontextprotocol.io/specification/2025-11-25
+ */
+ String MCP_2025_11_25 = "2025-11-25";
+
}
diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java
index 6ccf56d73..7ce12772c 100644
--- a/mcp-core/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java
+++ b/mcp-core/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java
@@ -536,11 +536,13 @@ void testNotificationHandlers() {
AtomicBoolean toolsNotificationReceived = new AtomicBoolean(false);
AtomicBoolean resourcesNotificationReceived = new AtomicBoolean(false);
AtomicBoolean promptsNotificationReceived = new AtomicBoolean(false);
+ AtomicBoolean resourcesUpdatedNotificationReceived = new AtomicBoolean(false);
withClient(createMcpTransport(),
builder -> builder.toolsChangeConsumer(tools -> toolsNotificationReceived.set(true))
.resourcesChangeConsumer(resources -> resourcesNotificationReceived.set(true))
- .promptsChangeConsumer(prompts -> promptsNotificationReceived.set(true)),
+ .promptsChangeConsumer(prompts -> promptsNotificationReceived.set(true))
+ .resourcesUpdateConsumer(resources -> resourcesUpdatedNotificationReceived.set(true)),
client -> {
assertThatCode(() -> {
diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java
index 0926eebae..6b0004cb9 100644
--- a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java
+++ b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java
@@ -1618,6 +1618,107 @@ void testListRootsResult() throws Exception {
}
+ // Elicitation Capability Tests (Issue #724)
+
+ @Test
+ void testElicitationCapabilityWithFormField() throws Exception {
+ // Test that elicitation with "form" field can be deserialized (2025-11-25 spec)
+ String json = """
+ {"protocolVersion":"2024-11-05","capabilities":{"elicitation":{"form":{}}},"clientInfo":{"name":"test-client","version":"1.0.0"}}
+ """;
+
+ McpSchema.InitializeRequest request = JSON_MAPPER.readValue(json, McpSchema.InitializeRequest.class);
+
+ assertThat(request).isNotNull();
+ assertThat(request.capabilities()).isNotNull();
+ assertThat(request.capabilities().elicitation()).isNotNull();
+ }
+
+ @Test
+ void testElicitationCapabilityWithFormAndUrlFields() throws Exception {
+ // Test that elicitation with both "form" and "url" fields can be deserialized
+ String json = """
+ {"protocolVersion":"2024-11-05","capabilities":{"elicitation":{"form":{},"url":{}}},"clientInfo":{"name":"test-client","version":"1.0.0"}}
+ """;
+
+ McpSchema.InitializeRequest request = JSON_MAPPER.readValue(json, McpSchema.InitializeRequest.class);
+
+ assertThat(request).isNotNull();
+ assertThat(request.capabilities()).isNotNull();
+ assertThat(request.capabilities().elicitation()).isNotNull();
+ }
+
+ @Test
+ void testElicitationCapabilityBackwardCompatibilityEmptyObject() throws Exception {
+ // Test backward compatibility: empty elicitation {} should still work
+ String json = """
+ {"protocolVersion":"2024-11-05","capabilities":{"elicitation":{}},"clientInfo":{"name":"test-client","version":"1.0.0"}}
+ """;
+
+ McpSchema.InitializeRequest request = JSON_MAPPER.readValue(json, McpSchema.InitializeRequest.class);
+
+ assertThat(request).isNotNull();
+ assertThat(request.capabilities()).isNotNull();
+ assertThat(request.capabilities().elicitation()).isNotNull();
+ }
+
+ @Test
+ void testElicitationCapabilityBuilderBackwardCompatibility() throws Exception {
+ // Test that the existing builder API still works and produces valid JSON
+ McpSchema.ClientCapabilities capabilities = McpSchema.ClientCapabilities.builder().elicitation().build();
+
+ assertThat(capabilities.elicitation()).isNotNull();
+
+ // Serialize and verify it produces valid JSON (should be {} for backward compat)
+ String json = JSON_MAPPER.writeValueAsString(capabilities);
+ assertThat(json).contains("\"elicitation\"");
+ }
+
+ @Test
+ void testElicitationCapabilitySerializationRoundTrip() throws Exception {
+ // Test that serialization and deserialization round-trip works
+ McpSchema.ClientCapabilities original = McpSchema.ClientCapabilities.builder().elicitation().build();
+
+ String json = JSON_MAPPER.writeValueAsString(original);
+ McpSchema.ClientCapabilities deserialized = JSON_MAPPER.readValue(json, McpSchema.ClientCapabilities.class);
+
+ assertThat(deserialized.elicitation()).isNotNull();
+ }
+
+ @Test
+ void testElicitationCapabilityBuilderWithFormAndUrl() throws Exception {
+ // Test the new builder method that explicitly sets form and url support
+ McpSchema.ClientCapabilities capabilities = McpSchema.ClientCapabilities.builder()
+ .elicitation(true, true)
+ .build();
+
+ assertThat(capabilities.elicitation()).isNotNull();
+ assertThat(capabilities.elicitation().form()).isNotNull();
+ assertThat(capabilities.elicitation().url()).isNotNull();
+
+ // Verify serialization produces the expected JSON
+ String json = JSON_MAPPER.writeValueAsString(capabilities);
+ assertThatJson(json).when(Option.IGNORING_ARRAY_ORDER).isObject().containsKey("elicitation");
+ assertThat(json).contains("\"form\"");
+ assertThat(json).contains("\"url\"");
+ }
+
+ @Test
+ void testElicitationCapabilityBuilderFormOnly() throws Exception {
+ // Test builder with form only
+ McpSchema.ClientCapabilities capabilities = McpSchema.ClientCapabilities.builder()
+ .elicitation(true, false)
+ .build();
+
+ assertThat(capabilities.elicitation()).isNotNull();
+ assertThat(capabilities.elicitation().form()).isNotNull();
+ assertThat(capabilities.elicitation().url()).isNull();
+
+ String json = JSON_MAPPER.writeValueAsString(capabilities);
+ assertThat(json).contains("\"form\"");
+ assertThat(json).doesNotContain("\"url\"");
+ }
+
// Progress Notification Tests
@Test
diff --git a/mcp-json-jackson2/pom.xml b/mcp-json-jackson2/pom.xml
index 45587c7bd..127380e2a 100644
--- a/mcp-json-jackson2/pom.xml
+++ b/mcp-json-jackson2/pom.xml
@@ -6,7 +6,7 @@
> ref = new TypeRef<>(){};
+ * Usage: TypeRef<List<Foo>> ref = new TypeRef<>(){};
*/
public abstract class TypeRef