Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -416,9 +416,47 @@ public record Sampling() {
* maintain control over user interactions and data sharing while enabling servers
* to gather necessary information dynamically. Servers can request structured
* data from users with optional JSON schemas to validate responses.
*
* <p>
* Per the 2025-11-25 spec, clients can declare support for specific elicitation
* modes:
* <ul>
* <li>{@code form} - In-band structured data collection with optional schema
* validation</li>
* <li>{@code url} - Out-of-band interaction via URL navigation</li>
* </ul>
*
* <p>
* 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() {
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down