Skip to content

Commit 92b1459

Browse files
authored
fix(schema): remove AddNullable from draft2020_12 settings (#664)
* fix(schema): remove AddNullable from draft2020_12 settings The `nullable` keyword is an OpenAPI 3.0 extension, not part of JSON Schema 2020-12. Using AddNullable with draft2020_12 settings causes validation failures with strict JSON Schema validators. JSON Schema 2020-12 represents nullable types using: - {"type": ["string", "null"]} (type array with null) - {"anyOf": [{"type": "string"}, {"type": "null"}]} Fixes #663 * test(schema): update complex schema nullable expectation * test(schema): align macro optional-field expectations with draft2020
1 parent 3df4c5b commit 92b1459

3 files changed

Lines changed: 24 additions & 21 deletions

File tree

crates/rmcp/src/handler/server/common.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ pub fn schema_for_type<T: JsonSchema + std::any::Any>() -> Arc<JsonObject> {
2323
} else {
2424
// explicitly to align json schema version to official specifications.
2525
// refer to https://github.com/modelcontextprotocol/modelcontextprotocol/pull/655 for details.
26-
let mut settings = SchemaSettings::draft2020_12();
27-
settings.transforms = vec![Box::new(schemars::transform::AddNullable::default())];
26+
let settings = SchemaSettings::draft2020_12();
27+
// Note: AddNullable is intentionally NOT used here because the `nullable` keyword
28+
// is an OpenAPI 3.0 extension, not part of JSON Schema 2020-12. Using it would
29+
// cause validation failures with strict JSON Schema validators.
2830
let generator = settings.into_generator();
2931
let schema = generator.into_root_schema_for::<T>();
3032
let object = serde_json::to_value(schema).expect("failed to serialize schema");

crates/rmcp/tests/test_complex_schema.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ fn expected_schema() -> serde_json::Value {
8080
"type": "array"
8181
},
8282
"system": {
83-
"nullable": true,
84-
"type": "string"
83+
"type": [
84+
"string",
85+
"null"
86+
]
8587
}
8688
},
8789
"required": [

crates/rmcp/tests/test_tool_macros.rs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ fn test_optional_field_schema_generation_via_macro() {
238238
);
239239

240240
// Verify the schema generated for the aggregated OptionalFieldTestSchema
241-
// by the macro infrastructure (which should now use OpenAPI 3 settings)
241+
// by the macro infrastructure using JSON Schema 2020-12 settings.
242242
let input_schema_map = &*tool_attr.input_schema; // Dereference Arc<JsonObject>
243243

244244
// Check the schema for the 'description' property within the input schema
@@ -253,18 +253,21 @@ fn test_optional_field_schema_generation_via_macro() {
253253
.as_object()
254254
.unwrap();
255255

256-
// Assert that the format is now `type: "string", nullable: true`
256+
// Assert nullable Option<T> is represented in JSON Schema 2020-12 form.
257+
let type_value = description_schema
258+
.get("type")
259+
.expect("Schema for Option<String> should include a type field");
260+
let type_array = type_value
261+
.as_array()
262+
.expect("Schema for Option<String> should use a type array [T, null]");
257263
assert_eq!(
258-
description_schema.get("type").map(|v| v.as_str().unwrap()),
259-
Some("string"),
260-
"Schema for Option<String> generated by macro should be type: \"string\""
264+
type_array,
265+
&vec![serde_json::json!("string"), serde_json::json!("null")],
266+
"Schema for Option<String> should be type: [\"string\", \"null\"]"
261267
);
262-
assert_eq!(
263-
description_schema
264-
.get("nullable")
265-
.map(|v| v.as_bool().unwrap()),
266-
Some(true),
267-
"Schema for Option<String> generated by macro should have nullable: true"
268+
assert!(
269+
description_schema.get("nullable").is_none(),
270+
"Schema for Option<String> should not use OpenAPI nullable in JSON Schema 2020-12"
268271
);
269272
// We still check the description is correct
270273
assert_eq!(
@@ -274,12 +277,8 @@ fn test_optional_field_schema_generation_via_macro() {
274277
Some("An optional description field")
275278
);
276279

277-
// Ensure the old 'type: [T, null]' format is NOT used
278-
let type_value = description_schema.get("type").unwrap();
279-
assert!(
280-
!type_value.is_array(),
281-
"Schema type should not be an array [T, null]"
282-
);
280+
// Ensure no OpenAPI-only nullable extension was emitted.
281+
assert!(description_schema.get("nullable").is_none());
283282
}
284283

285284
// Define a dummy client handler

0 commit comments

Comments
 (0)