Skip to main content

component_shape_mcp/
schema.rs

1use super::*;
2
3/// Primitive JSON Schema root types supported by typed schema builders.
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub enum McpSchemaType {
6    Null,
7    Boolean,
8    Integer,
9    Number,
10    String,
11    Array,
12    Object,
13}
14
15impl McpSchemaType {
16    /// Returns the JSON Schema type keyword value.
17    pub const fn as_str(self) -> &'static str {
18        match self {
19            Self::Null => "null",
20            Self::Boolean => "boolean",
21            Self::Integer => "integer",
22            Self::Number => "number",
23            Self::String => "string",
24            Self::Array => "array",
25            Self::Object => "object",
26        }
27    }
28}
29
30/// Standard string formats emitted by component-shape MCP schemas.
31#[derive(Clone, Copy, Debug, Eq, PartialEq)]
32pub enum McpStringFormat {
33    Date,
34    DateTime,
35}
36
37impl McpStringFormat {
38    /// Returns the JSON Schema string format keyword value.
39    pub const fn as_str(self) -> &'static str {
40        match self {
41            Self::Date => "date",
42            Self::DateTime => "date-time",
43        }
44    }
45}
46
47/// JSON number used by numeric JSON Schema keywords.
48#[derive(Clone, Debug, Eq, PartialEq)]
49pub struct McpSchemaNumber(Number);
50
51impl McpSchemaNumber {
52    /// Converts a finite `f64` into a JSON Schema number.
53    pub fn from_f64(value: f64) -> Option<Self> {
54        Number::from_f64(value).map(Self)
55    }
56
57    /// Converts this number into a JSON value.
58    pub fn into_value(self) -> Value {
59        Value::Number(self.0)
60    }
61}
62
63macro_rules! impl_schema_number {
64    ($($ty:ty),* $(,)?) => {
65        $(
66            impl From<$ty> for McpSchemaNumber {
67                fn from(value: $ty) -> Self {
68                    Self(Number::from(value))
69                }
70            }
71        )*
72    };
73}
74
75impl_schema_number!(i8, i16, i32, i64, u8, u16, u32, u64);
76
77impl From<isize> for McpSchemaNumber {
78    fn from(value: isize) -> Self {
79        Self(Number::from(value as i64))
80    }
81}
82
83impl From<usize> for McpSchemaNumber {
84    fn from(value: usize) -> Self {
85        Self(Number::from(value as u64))
86    }
87}
88
89/// Value accepted by the JSON Schema `additionalProperties` keyword.
90pub enum McpAdditionalProperties {
91    /// Whether additional properties are allowed.
92    Allowed(bool),
93    /// Schema applied to additional property values.
94    Schema(McpSchema),
95}
96
97impl From<bool> for McpAdditionalProperties {
98    fn from(value: bool) -> Self {
99        Self::Allowed(value)
100    }
101}
102
103impl From<McpSchema> for McpAdditionalProperties {
104    fn from(schema: McpSchema) -> Self {
105        Self::Schema(schema)
106    }
107}
108
109/// Typed JSON Schema value used by generated MCP tool metadata.
110///
111/// This wrapper keeps schema-producing APIs distinct from arbitrary tool-call
112/// JSON while still exposing the underlying [`serde_json::Value`] for tests,
113/// protocol handoff, and last-mile schema extension.
114#[derive(Clone, Debug, Eq, PartialEq)]
115pub struct McpSchema(Value);
116
117impl McpSchema {
118    pub fn new(schema: Value) -> Self {
119        Self(schema)
120    }
121
122    pub fn any() -> Self {
123        Self(Value::Object(Map::new()))
124    }
125
126    pub fn impossible() -> Self {
127        Self::any().with_not(Self::any())
128    }
129
130    pub fn typed(schema_type: McpSchemaType) -> Self {
131        let mut object = Map::new();
132        object.insert(
133            "type".to_string(),
134            Value::String(schema_type.as_str().to_string()),
135        );
136        Self(Value::Object(object))
137    }
138
139    pub fn null() -> Self {
140        Self::typed(McpSchemaType::Null)
141    }
142
143    pub fn boolean() -> Self {
144        Self::typed(McpSchemaType::Boolean)
145    }
146
147    pub fn integer() -> Self {
148        Self::typed(McpSchemaType::Integer)
149    }
150
151    pub fn number() -> Self {
152        Self::typed(McpSchemaType::Number)
153    }
154
155    pub fn string() -> Self {
156        Self::typed(McpSchemaType::String)
157    }
158
159    pub fn array(item_schema: McpSchema) -> Self {
160        Self::typed(McpSchemaType::Array).with_items(item_schema)
161    }
162
163    pub fn object() -> Self {
164        Self::typed(McpSchemaType::Object)
165    }
166
167    pub fn any_of<I>(schemas: I) -> Self
168    where
169        I: IntoIterator<Item = McpSchema>,
170    {
171        let mut schema = Self::any();
172        schema.set_any_of(schemas);
173        schema
174    }
175
176    pub fn one_of<I>(schemas: I) -> Self
177    where
178        I: IntoIterator<Item = McpSchema>,
179    {
180        let mut schema = Self::any();
181        schema.set_one_of(schemas);
182        schema
183    }
184
185    pub fn with_description(mut self, description: impl Into<String>) -> Self {
186        self.set_description(description);
187        self
188    }
189
190    pub fn with_extension(mut self, key: impl Into<String>, value: Value) -> Self {
191        self.set_extension(key, value);
192        self
193    }
194
195    pub fn with_format(mut self, format: McpStringFormat) -> Self {
196        self.set_format(format);
197        self
198    }
199
200    pub fn with_minimum(mut self, minimum: impl Into<McpSchemaNumber>) -> Self {
201        self.set_minimum(minimum);
202        self
203    }
204
205    pub fn with_min_items(mut self, min_items: usize) -> Self {
206        self.set_min_items(min_items);
207        self
208    }
209
210    pub fn with_max_items(mut self, max_items: usize) -> Self {
211        self.set_max_items(max_items);
212        self
213    }
214
215    pub fn with_unique_items(mut self, unique_items: bool) -> Self {
216        self.set_unique_items(unique_items);
217        self
218    }
219
220    pub fn with_default(mut self, value: impl Into<Value>) -> Self {
221        self.set_default(value);
222        self
223    }
224
225    pub fn with_const(mut self, value: impl Into<Value>) -> Self {
226        self.set_const(value);
227        self
228    }
229
230    pub fn with_enum_values<I, V>(mut self, values: I) -> Self
231    where
232        I: IntoIterator<Item = V>,
233        V: Into<Value>,
234    {
235        self.set_enum_values(values);
236        self
237    }
238
239    pub fn with_items(mut self, item_schema: McpSchema) -> Self {
240        self.set_items(item_schema);
241        self
242    }
243
244    pub fn with_prefix_items<I>(mut self, item_schemas: I) -> Self
245    where
246        I: IntoIterator<Item = McpSchema>,
247    {
248        self.set_prefix_items(item_schemas);
249        self
250    }
251
252    pub fn with_properties(mut self, properties: McpSchemaProperties) -> Self {
253        self.set_properties(properties);
254        self
255    }
256
257    pub fn with_required<I, S>(mut self, required: I) -> Self
258    where
259        I: IntoIterator<Item = S>,
260        S: Into<String>,
261    {
262        self.set_required(required);
263        self
264    }
265
266    pub fn with_additional_properties(
267        mut self,
268        additional_properties: impl Into<McpAdditionalProperties>,
269    ) -> Self {
270        self.set_additional_properties(additional_properties);
271        self
272    }
273
274    pub fn with_not(mut self, schema: McpSchema) -> Self {
275        self.set_not(schema);
276        self
277    }
278
279    pub fn set_description(&mut self, description: impl Into<String>) {
280        self.set_extension("description", Value::String(description.into()));
281    }
282
283    pub fn set_extension(&mut self, key: impl Into<String>, value: Value) {
284        if let Some(object) = self.as_object_mut() {
285            object.insert(key.into(), value);
286        }
287    }
288
289    pub fn set_format(&mut self, format: McpStringFormat) {
290        self.set_extension("format", Value::String(format.as_str().to_string()));
291    }
292
293    pub fn set_minimum(&mut self, minimum: impl Into<McpSchemaNumber>) {
294        self.set_extension("minimum", minimum.into().into_value());
295    }
296
297    pub fn set_min_items(&mut self, min_items: usize) {
298        self.set_extension("minItems", Value::Number(Number::from(min_items as u64)));
299    }
300
301    pub fn set_max_items(&mut self, max_items: usize) {
302        self.set_extension("maxItems", Value::Number(Number::from(max_items as u64)));
303    }
304
305    pub fn set_unique_items(&mut self, unique_items: bool) {
306        self.set_extension("uniqueItems", Value::Bool(unique_items));
307    }
308
309    pub fn set_default(&mut self, value: impl Into<Value>) {
310        self.set_extension("default", value.into());
311    }
312
313    pub fn set_const(&mut self, value: impl Into<Value>) {
314        self.set_extension("const", value.into());
315    }
316
317    pub fn set_enum_values<I, V>(&mut self, values: I)
318    where
319        I: IntoIterator<Item = V>,
320        V: Into<Value>,
321    {
322        self.set_extension(
323            "enum",
324            Value::Array(values.into_iter().map(Into::into).collect()),
325        );
326    }
327
328    pub fn set_items(&mut self, item_schema: McpSchema) {
329        self.set_extension("items", item_schema.into_value());
330    }
331
332    pub fn set_prefix_items<I>(&mut self, item_schemas: I)
333    where
334        I: IntoIterator<Item = McpSchema>,
335    {
336        self.set_extension(
337            "prefixItems",
338            Value::Array(
339                item_schemas
340                    .into_iter()
341                    .map(McpSchema::into_value)
342                    .collect(),
343            ),
344        );
345    }
346
347    pub fn set_properties(&mut self, properties: McpSchemaProperties) {
348        self.set_extension(
349            "properties",
350            Value::Object(
351                properties
352                    .into_iter()
353                    .map(|(name, schema)| (name, schema.into_value()))
354                    .collect(),
355            ),
356        );
357    }
358
359    pub fn set_required<I, S>(&mut self, required: I)
360    where
361        I: IntoIterator<Item = S>,
362        S: Into<String>,
363    {
364        self.set_extension(
365            "required",
366            Value::Array(
367                required
368                    .into_iter()
369                    .map(|field| Value::String(field.into()))
370                    .collect(),
371            ),
372        );
373    }
374
375    pub fn set_additional_properties(
376        &mut self,
377        additional_properties: impl Into<McpAdditionalProperties>,
378    ) {
379        let value = match additional_properties.into() {
380            McpAdditionalProperties::Allowed(value) => Value::Bool(value),
381            McpAdditionalProperties::Schema(schema) => schema.into_value(),
382        };
383        self.set_extension("additionalProperties", value);
384    }
385
386    pub fn set_any_of<I>(&mut self, schemas: I)
387    where
388        I: IntoIterator<Item = McpSchema>,
389    {
390        self.set_extension(
391            "anyOf",
392            Value::Array(schemas.into_iter().map(McpSchema::into_value).collect()),
393        );
394    }
395
396    pub fn set_one_of<I>(&mut self, schemas: I)
397    where
398        I: IntoIterator<Item = McpSchema>,
399    {
400        self.set_extension(
401            "oneOf",
402            Value::Array(schemas.into_iter().map(McpSchema::into_value).collect()),
403        );
404    }
405
406    pub fn set_not(&mut self, schema: McpSchema) {
407        self.set_extension("not", schema.into_value());
408    }
409
410    pub fn as_value(&self) -> &Value {
411        &self.0
412    }
413
414    pub fn as_object_mut(&mut self) -> Option<&mut Map<String, Value>> {
415        self.0.as_object_mut()
416    }
417
418    pub fn into_value(self) -> Value {
419        self.0
420    }
421}
422
423impl<I> Index<I> for McpSchema
424where
425    Value: Index<I>,
426{
427    type Output = <Value as Index<I>>::Output;
428
429    fn index(&self, index: I) -> &Self::Output {
430        &self.0[index]
431    }
432}
433
434/// JSON Schema metadata for typed MCP argument and result values.
435///
436/// Runtime integrations use this trait when a Rust type can describe its
437/// structured JSON shape more precisely than [`McpInput`]'s coarse object
438/// marker. Type aliases inherit the implementation of their target type, and
439/// app-owned newtypes or structs can derive this trait with
440/// `#[derive(component_shape_mcp::McpJsonSchema)]`. `serde_json::Value`
441/// publishes an unconstrained schema for dynamic argument fields.
442pub trait McpJsonSchema {
443    /// Returns the JSON Schema for this type.
444    fn json_schema() -> McpSchema;
445}
446
447/// Explicit unconstrained JSON value for typed MCP inputs.
448///
449/// Use this instead of `serde_json::Value` in `McpToolInput` or
450/// `McpToolValue` positions when a field intentionally accepts any JSON. Raw
451/// protocol and server internals still use `serde_json::Value` directly.
452#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
453#[serde(transparent)]
454pub struct McpAny(Value);
455
456impl McpAny {
457    /// Creates an unconstrained MCP value from raw JSON.
458    pub fn new(value: Value) -> Self {
459        Self(value)
460    }
461
462    /// Returns the raw JSON value.
463    pub fn as_value(&self) -> &Value {
464        &self.0
465    }
466
467    /// Returns the raw JSON value.
468    pub fn into_value(self) -> Value {
469        self.0
470    }
471}
472
473impl From<Value> for McpAny {
474    fn from(value: Value) -> Self {
475        Self::new(value)
476    }
477}
478
479impl From<McpAny> for Value {
480    fn from(value: McpAny) -> Self {
481        value.into_value()
482    }
483}
484
485impl std::ops::Deref for McpAny {
486    type Target = Value;
487
488    fn deref(&self) -> &Self::Target {
489        self.as_value()
490    }
491}
492
493impl McpJsonSchema for McpAny {
494    fn json_schema() -> McpSchema {
495        McpSchema::any()
496    }
497}
498
499impl McpJsonSchema for Value {
500    fn json_schema() -> McpSchema {
501        McpSchema::any()
502    }
503}
504
505/// Typed MCP tool input decoded from a protocol argument object.
506///
507/// Derive this on app-owned form submit or table query argument structs when a
508/// generated integration should publish the JSON schema and pass a typed value
509/// to the tool handler. Field schemas and decoders are paired through
510/// [`McpToolValue`]. The derive also implements [`McpJsonSchema`] for the input
511/// struct, so object-shaped tool inputs can be reused as nested field or filter
512/// values.
513pub trait McpToolInput: Sized {
514    /// Returns the input object's JSON Schema.
515    fn input_schema() -> McpSchema;
516
517    /// Decodes a normalized tool call into this input type.
518    ///
519    /// # Errors
520    ///
521    /// Returns [`McpToolError`] when required fields are missing, unknown fields
522    /// remain, a field value fails schema validation, or serde rejects a field.
523    fn from_tool_call(call: McpToolCall) -> Result<Self, McpToolError>;
524}
525
526impl McpToolInput for () {
527    fn input_schema() -> McpSchema {
528        object_schema(McpSchemaProperties::new(), std::iter::empty::<&str>())
529    }
530
531    fn from_tool_call(call: McpToolCall) -> Result<Self, McpToolError> {
532        call.into_arguments().finish()
533    }
534}
535
536/// Tool definition paired with the typed MCP input it was built from.
537///
538/// `McpServer::add_typed_tool` consumes this wrapper so a typed handler cannot
539/// accidentally be registered with another input type's schema.
540#[derive(Clone)]
541pub struct McpTypedTool<Input> {
542    definition: ToolDefinition,
543    _input: PhantomData<fn() -> Input>,
544}
545
546impl<Input> McpTypedTool<Input> {
547    pub(crate) fn from_definition_unchecked(definition: ToolDefinition) -> Self {
548        Self {
549            definition,
550            _input: PhantomData,
551        }
552    }
553
554    /// Returns the wrapped MCP tool definition.
555    pub fn definition(&self) -> &ToolDefinition {
556        &self.definition
557    }
558
559    /// Returns the wrapped MCP tool definition mutably.
560    pub fn definition_mut(&mut self) -> &mut ToolDefinition {
561        &mut self.definition
562    }
563
564    /// Returns the wrapped MCP tool definition.
565    pub fn into_definition(self) -> ToolDefinition {
566        self.definition
567    }
568}
569
570impl<Input> AsRef<ToolDefinition> for McpTypedTool<Input> {
571    fn as_ref(&self) -> &ToolDefinition {
572        self.definition()
573    }
574}
575
576impl<Input> std::ops::Deref for McpTypedTool<Input> {
577    type Target = ToolDefinition;
578
579    fn deref(&self) -> &Self::Target {
580        self.definition()
581    }
582}
583
584/// Typed value used as one field inside an MCP tool argument object.
585///
586/// This is the value-level companion to [`McpToolInput`]. Generated form and
587/// table integrations use it when a single field or filter value must provide
588/// both JSON Schema metadata and strict serde-based decoding. Any type that
589/// implements [`McpJsonSchema`] and [`serde::de::DeserializeOwned`] gets the
590/// default implementation; JSON null is accepted only when the generated schema
591/// allows null.
592pub trait McpToolValue: Sized {
593    /// Returns the schema used for this value inside an MCP argument object.
594    fn tool_value_schema() -> McpSchema;
595
596    /// Decodes one JSON field value into this type.
597    ///
598    /// # Errors
599    ///
600    /// Returns [`McpToolError`] when the value is unexpectedly null, fails
601    /// schema validation, or serde rejects the normalized JSON value.
602    fn from_tool_value(field: &str, value: Value) -> Result<Self, McpToolError>;
603}
604
605impl<T> McpToolValue for T
606where
607    T: McpJsonSchema + DeserializeOwned,
608{
609    fn tool_value_schema() -> McpSchema {
610        T::json_schema()
611    }
612
613    fn from_tool_value(field: &str, value: Value) -> Result<Self, McpToolError> {
614        let schema = T::json_schema();
615        if value.is_null() && !schema_allows_null(&schema) {
616            return Err(McpToolError::UnexpectedNull {
617                field: field.to_string(),
618            });
619        }
620        validate_value_against_closed_schema(field, schema.as_value(), &value)?;
621        let value = normalize_value_against_schema(schema.as_value(), value);
622
623        serde_json::from_value(value).map_err(|error| McpToolError::DecodeField {
624            field: field.to_string(),
625            message: error.to_string(),
626        })
627    }
628}
629
630/// Typed `{ "min": ..., "max": ... }` range argument used by MCP tools.
631///
632/// Custom table filter shapes can use this as their `RawValue` when they want
633/// derive-driven MCP decoding for range-shaped arguments.
634#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
635#[serde(deny_unknown_fields)]
636pub struct McpRange<T> {
637    /// Inclusive lower range bound.
638    pub min: Option<T>,
639    /// Inclusive upper range bound.
640    pub max: Option<T>,
641}
642
643impl<T> McpRange<T> {
644    /// Creates a range argument from optional bounds.
645    pub fn new(min: Option<T>, max: Option<T>) -> Self {
646        Self { min, max }
647    }
648
649    /// Returns this range as `(min, max)`.
650    pub fn into_tuple(self) -> (Option<T>, Option<T>) {
651        (self.min, self.max)
652    }
653}
654
655impl<T> From<(Option<T>, Option<T>)> for McpRange<T> {
656    fn from((min, max): (Option<T>, Option<T>)) -> Self {
657        Self::new(min, max)
658    }
659}
660
661impl<T> From<McpRange<T>> for (Option<T>, Option<T>) {
662    fn from(value: McpRange<T>) -> Self {
663        value.into_tuple()
664    }
665}
666
667impl<T> McpJsonSchema for McpRange<T>
668where
669    T: McpJsonSchema,
670{
671    fn json_schema() -> McpSchema {
672        range_schema(T::json_schema())
673    }
674}
675
676impl McpJsonSchema for bool {
677    fn json_schema() -> McpSchema {
678        schema_for_primitive(McpPrimitiveKind::Boolean)
679    }
680}
681
682macro_rules! impl_integer_schema {
683    ($($ty:ty),* $(,)?) => {
684        $(
685            impl McpJsonSchema for $ty {
686                fn json_schema() -> McpSchema {
687                    schema_for_primitive(McpPrimitiveKind::Integer)
688                }
689            }
690        )*
691    };
692}
693
694impl_integer_schema!(
695    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize,
696);
697
698macro_rules! impl_number_schema {
699    ($($ty:ty),* $(,)?) => {
700        $(
701            impl McpJsonSchema for $ty {
702                fn json_schema() -> McpSchema {
703                    schema_for_primitive(McpPrimitiveKind::Number)
704                }
705            }
706        )*
707    };
708}
709
710impl_number_schema!(f32, f64);
711
712#[cfg(feature = "rust_decimal")]
713impl McpJsonSchema for rust_decimal::Decimal {
714    fn json_schema() -> McpSchema {
715        schema_for_primitive(McpPrimitiveKind::Decimal)
716    }
717}
718
719impl McpJsonSchema for String {
720    fn json_schema() -> McpSchema {
721        schema_for_primitive(McpPrimitiveKind::String)
722    }
723}
724
725impl McpJsonSchema for str {
726    fn json_schema() -> McpSchema {
727        schema_for_primitive(McpPrimitiveKind::String)
728    }
729}
730
731impl McpJsonSchema for char {
732    fn json_schema() -> McpSchema {
733        schema_for_primitive(McpPrimitiveKind::String)
734    }
735}
736
737impl McpJsonSchema for PathBuf {
738    fn json_schema() -> McpSchema {
739        schema_for_primitive(McpPrimitiveKind::String)
740    }
741}
742
743impl McpJsonSchema for Path {
744    fn json_schema() -> McpSchema {
745        schema_for_primitive(McpPrimitiveKind::String)
746    }
747}
748
749#[cfg(feature = "chrono")]
750impl McpJsonSchema for chrono::NaiveDate {
751    fn json_schema() -> McpSchema {
752        schema_for_primitive(McpPrimitiveKind::Date)
753    }
754}
755
756#[cfg(feature = "chrono")]
757impl McpJsonSchema for chrono::NaiveDateTime {
758    fn json_schema() -> McpSchema {
759        schema_for_primitive(McpPrimitiveKind::DateTime)
760    }
761}
762
763#[cfg(feature = "chrono")]
764impl<Tz> McpJsonSchema for chrono::DateTime<Tz>
765where
766    Tz: chrono::TimeZone,
767{
768    fn json_schema() -> McpSchema {
769        schema_for_primitive(McpPrimitiveKind::DateTime)
770    }
771}
772
773impl<T> McpJsonSchema for Option<T>
774where
775    T: McpJsonSchema,
776{
777    fn json_schema() -> McpSchema {
778        nullable_schema(T::json_schema())
779    }
780}
781
782impl<T> McpJsonSchema for Vec<T>
783where
784    T: McpJsonSchema,
785{
786    fn json_schema() -> McpSchema {
787        array_schema(T::json_schema())
788    }
789}
790
791impl<T> McpJsonSchema for [T]
792where
793    T: McpJsonSchema,
794{
795    fn json_schema() -> McpSchema {
796        array_schema(T::json_schema())
797    }
798}
799
800impl<T, const N: usize> McpJsonSchema for [T; N]
801where
802    T: McpJsonSchema,
803{
804    fn json_schema() -> McpSchema {
805        let mut schema = array_schema(T::json_schema());
806        if let Some(object) = schema.as_object_mut() {
807            object.insert("minItems".to_string(), Value::Number((N as u64).into()));
808            object.insert("maxItems".to_string(), Value::Number((N as u64).into()));
809        }
810        schema
811    }
812}
813
814macro_rules! impl_tuple_schema {
815    ($($name:ident),+ $(,)?) => {
816        impl<$($name),+> McpJsonSchema for ($($name,)+)
817        where
818            $($name: McpJsonSchema,)+
819        {
820            fn json_schema() -> McpSchema {
821                tuple_schema([
822                    $(<$name as McpJsonSchema>::json_schema(),)+
823                ])
824            }
825        }
826    };
827}
828
829impl_tuple_schema!(A);
830impl_tuple_schema!(A, B);
831impl_tuple_schema!(A, B, C);
832impl_tuple_schema!(A, B, C, D);
833
834impl<T> McpJsonSchema for BTreeSet<T>
835where
836    T: McpJsonSchema,
837{
838    fn json_schema() -> McpSchema {
839        unique_array_schema(T::json_schema())
840    }
841}
842
843impl<T> McpJsonSchema for HashSet<T>
844where
845    T: Eq + Hash + McpJsonSchema,
846{
847    fn json_schema() -> McpSchema {
848        unique_array_schema(T::json_schema())
849    }
850}
851
852impl<T> McpJsonSchema for BTreeMap<String, T>
853where
854    T: McpJsonSchema,
855{
856    fn json_schema() -> McpSchema {
857        string_keyed_object_schema(T::json_schema())
858    }
859}
860
861impl<T> McpJsonSchema for HashMap<String, T>
862where
863    T: McpJsonSchema,
864{
865    fn json_schema() -> McpSchema {
866        string_keyed_object_schema(T::json_schema())
867    }
868}
869
870impl<T> McpJsonSchema for Map<String, T>
871where
872    T: McpJsonSchema,
873{
874    fn json_schema() -> McpSchema {
875        string_keyed_object_schema(T::json_schema())
876    }
877}
878
879impl<T> McpJsonSchema for &T
880where
881    T: ?Sized + McpJsonSchema,
882{
883    fn json_schema() -> McpSchema {
884        T::json_schema()
885    }
886}
887
888impl<'a, T> McpJsonSchema for Cow<'a, T>
889where
890    T: ?Sized + ToOwned + McpJsonSchema,
891{
892    fn json_schema() -> McpSchema {
893        T::json_schema()
894    }
895}
896
897/// Build an array schema for items of `item_schema`.
898pub fn array_schema(item_schema: McpSchema) -> McpSchema {
899    McpSchema::array(item_schema)
900}
901
902/// Build a unique-array schema for items of `item_schema`.
903pub fn unique_array_schema(item_schema: McpSchema) -> McpSchema {
904    array_schema(item_schema).with_unique_items(true)
905}
906
907/// Build a fixed-length tuple schema.
908pub fn tuple_schema<I>(item_schemas: I) -> McpSchema
909where
910    I: IntoIterator<Item = McpSchema>,
911{
912    let prefix_items = item_schemas.into_iter().collect::<Vec<_>>();
913    let len = prefix_items.len();
914
915    McpSchema::typed(McpSchemaType::Array)
916        .with_prefix_items(prefix_items)
917        .with_min_items(len)
918        .with_max_items(len)
919}
920
921/// Build an object schema whose string keys all use `value_schema`.
922pub fn string_keyed_object_schema(value_schema: McpSchema) -> McpSchema {
923    McpSchema::object().with_additional_properties(value_schema)
924}
925
926/// Build a closed object schema from properties and required field names.
927pub fn object_schema<I, S>(properties: McpSchemaProperties, required: I) -> McpSchema
928where
929    I: IntoIterator<Item = S>,
930    S: Into<String>,
931{
932    McpSchema::object()
933        .with_properties(properties)
934        .with_required(required)
935        .with_additional_properties(false)
936}
937
938impl<T> McpJsonSchema for Box<T>
939where
940    T: McpJsonSchema,
941{
942    fn json_schema() -> McpSchema {
943        T::json_schema()
944    }
945}
946
947impl McpJsonSchema for () {
948    fn json_schema() -> McpSchema {
949        McpSchema::null()
950    }
951}