diff --git a/crates/rmcp/src/transport/common/mcp_headers.rs b/crates/rmcp/src/transport/common/mcp_headers.rs index fe75b34b9..294abb7bc 100644 --- a/crates/rmcp/src/transport/common/mcp_headers.rs +++ b/crates/rmcp/src/transport/common/mcp_headers.rs @@ -211,8 +211,8 @@ fn has_off_chain_annotation(schema: &JsonObject, on_chain: bool) -> bool { "$defs" | "definitions" | "dependentSchemas" | "patternProperties" => { schema_map_has_off_chain_annotation(value, false) } - // Instance data, not subschemas. - "default" | "const" | "enum" | "examples" => false, + // These contain instance data or property names, not subschemas. + "default" | "const" | "enum" | "examples" | "dependentRequired" => false, _ => subschema_has_off_chain_annotation(value, false), }) } @@ -735,6 +735,35 @@ mod tests { assert!(validate_param_header_annotations(&schema).is_err()); } + #[test] + fn accepts_property_name_in_dependent_required() { + let schema = json!({ + "type": "object", + "properties": { + "x-mcp-header": { "type": "string" }, + "tenant": { "type": "string", "x-mcp-header": "Tenant" } + }, + "dependentRequired": { "x-mcp-header": ["tenant"] } + }); + assert_eq!( + validate_param_header_annotations(schema.as_object().unwrap()), + Ok(()) + ); + } + + #[test] + fn rejects_header_annotation_inside_dependent_schema() { + let schema = json!({ + "type": "object", + "dependentSchemas": { + "tenant": { + "properties": { "region": { "type": "string", "x-mcp-header": "Region" } } + } + } + }); + assert!(validate_param_header_annotations(schema.as_object().unwrap()).is_err()); + } + #[test] fn ignores_annotation_key_in_default_value() { let schema = schema_with(json!({ diff --git a/crates/rmcp/src/transport/streamable_http_client.rs b/crates/rmcp/src/transport/streamable_http_client.rs index db44ab3fb..8b964be94 100644 --- a/crates/rmcp/src/transport/streamable_http_client.rs +++ b/crates/rmcp/src/transport/streamable_http_client.rs @@ -2603,6 +2603,46 @@ mod tests { ); } + #[test] + fn cache_tools_keeps_property_names_in_dependent_required() { + let valid = tool( + "valid", + json!({ + "type": "object", + "properties": { + "x-mcp-header": { "type": "string" }, + "tenant": { "type": "string", "x-mcp-header": "Tenant" } + }, + "dependentRequired": { "x-mcp-header": ["tenant"] } + }), + ); + let mut message = ServerJsonRpcMessage::response( + ServerResult::ListToolsResult(ListToolsResult::with_all_items(vec![valid])), + NumberOrString::Number(1), + ); + let mut cache = HashMap::new(); + + cache_tools_from_response(&mut cache, &mut message, &ProtocolVersion::V_2026_07_28); + + let ServerJsonRpcMessage::Response(response) = message else { + panic!("expected tools/list response"); + }; + let ServerResult::ListToolsResult(result) = response.result else { + panic!("expected tools/list result"); + }; + assert_eq!( + ( + result + .tools + .iter() + .map(|tool| tool.name.as_ref()) + .collect::>(), + cache.keys().map(String::as_str).collect::>(), + ), + (vec!["valid"], vec!["valid"]) + ); + } + #[test] fn cache_tools_keeps_nested_annotations_and_drops_array_annotations() { let nested = tool(