feat: client-side application of elicitation schema defaults (SEP-1034) (#976)
Adds an opt-in McpClient builder option `applyElicitationDefaults(boolean)` that, when enabled, fills missing keys of an accepted ElicitResult.content with `default` values declared in the requestedSchema before returning the result to the server. Mirrors the TypeScript SDK's applyElicitationDefaults behavior, but exposed as a local client config rather than a wire capability. Co-authored-by: Sainath Reddy Bobbala <bsnr@amazon.com> Signed-off-by: Dariusz Jędrzejczyk <2554306+chemicL@users.noreply.github.com>
This commit is contained in:
parent
6b71136224
commit
accba74c8f
1 changed files with 587 additions and 18 deletions
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright 2024-2026 the original author or authors.
|
||||
*/
|
||||
|
||||
package io.modelcontextprotocol.client;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link McpAsyncClient#applyElicitationDefaults(Map, Map)}.
|
||||
*
|
||||
* Verifies that the client-side default application logic correctly fills in missing
|
||||
* fields from schema defaults, matching the behavior specified in SEP-1034.
|
||||
*/
|
||||
class McpAsyncClientElicitationDefaultsTests {
|
||||
|
||||
@Test
|
||||
void appliesStringDefault() {
|
||||
Map<String, Object> schema = Map.of("properties", Map.of("name", Map.of("type", "string", "default", "Guest")));
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).containsEntry("name", "Guest");
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliesNumberDefault() {
|
||||
Map<String, Object> schema = Map.of("properties", Map.of("age", Map.of("type", "integer", "default", 18)));
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).containsEntry("age", 18);
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliesBooleanDefault() {
|
||||
Map<String, Object> schema = Map.of("properties",
|
||||
Map.of("subscribe", Map.of("type", "boolean", "default", true)));
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).containsEntry("subscribe", true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliesEnumDefault() {
|
||||
Map<String, Object> schema = Map.of("properties",
|
||||
Map.of("color", Map.of("type", "string", "enum", List.of("red", "green"), "default", "green")));
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).containsEntry("color", "green");
|
||||
}
|
||||
|
||||
@Test
|
||||
void doesNotOverrideExistingValues() {
|
||||
Map<String, Object> schema = Map.of("properties", Map.of("name", Map.of("type", "string", "default", "Guest")));
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
content.put("name", "Alice");
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).containsEntry("name", "Alice");
|
||||
}
|
||||
|
||||
@Test
|
||||
void skipsPropertiesWithoutDefault() {
|
||||
Map<String, Object> schema = Map.of("properties", Map.of("email", Map.of("type", "string")));
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).doesNotContainKey("email");
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliesMultipleDefaults() {
|
||||
Map<String, Object> schema = Map.of("properties",
|
||||
Map.of("name", Map.of("type", "string", "default", "Guest"), "age",
|
||||
Map.of("type", "integer", "default", 18), "subscribe",
|
||||
Map.of("type", "boolean", "default", true), "color",
|
||||
Map.of("type", "string", "enum", List.of("red", "green"), "default", "green")));
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).containsEntry("name", "Guest")
|
||||
.containsEntry("age", 18)
|
||||
.containsEntry("subscribe", true)
|
||||
.containsEntry("color", "green");
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesNullSchema() {
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
McpAsyncClient.applyElicitationDefaults(null, content);
|
||||
|
||||
assertThat(content).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesNullContent() {
|
||||
Map<String, Object> schema = Map.of("properties", Map.of("name", Map.of("type", "string", "default", "Guest")));
|
||||
|
||||
// Should not throw
|
||||
McpAsyncClient.applyElicitationDefaults(schema, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void handlesSchemaWithoutProperties() {
|
||||
Map<String, Object> schema = Map.of("type", "object");
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliesDefaultsOnlyToMissingFields() {
|
||||
Map<String, Object> schema = Map.of("properties", Map.of("name", Map.of("type", "string", "default", "Guest"),
|
||||
"age", Map.of("type", "integer", "default", 18)));
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
content.put("name", "John");
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).containsEntry("name", "John").containsEntry("age", 18);
|
||||
}
|
||||
|
||||
@Test
|
||||
void appliesFloatingPointDefault() {
|
||||
Map<String, Object> schema = Map.of("properties", Map.of("score", Map.of("type", "number", "default", 95.5)));
|
||||
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
McpAsyncClient.applyElicitationDefaults(schema, content);
|
||||
|
||||
assertThat(content).containsEntry("score", 95.5);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue