agenterra_rmcp/model/
capabilities.rs

1use std::{collections::BTreeMap, marker::PhantomData};
2
3use paste::paste;
4use serde::{Deserialize, Serialize};
5
6use super::JsonObject;
7pub type ExperimentalCapabilities = BTreeMap<String, JsonObject>;
8
9#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
10#[serde(rename_all = "camelCase")]
11#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
12pub struct PromptsCapability {
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub list_changed: Option<bool>,
15}
16
17#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
18#[serde(rename_all = "camelCase")]
19#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
20pub struct ResourcesCapability {
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub subscribe: Option<bool>,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub list_changed: Option<bool>,
25}
26
27#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
28#[serde(rename_all = "camelCase")]
29#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
30pub struct ToolsCapability {
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub list_changed: Option<bool>,
33}
34
35#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
36#[serde(rename_all = "camelCase")]
37#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
38pub struct RootsCapabilities {
39    pub list_changed: Option<bool>,
40}
41
42///
43/// # Builder
44/// ```rust
45/// # use agenterra_rmcp::model::ClientCapabilities;
46/// let cap = ClientCapabilities::builder()
47///     .enable_experimental()
48///     .enable_roots()
49///     .enable_roots_list_changed()
50///     .build();
51/// ```
52#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
53#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
54pub struct ClientCapabilities {
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub experimental: Option<ExperimentalCapabilities>,
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub roots: Option<RootsCapabilities>,
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub sampling: Option<JsonObject>,
61}
62
63///
64/// ## Builder
65/// ```rust
66/// # use agenterra_rmcp::model::ServerCapabilities;
67/// let cap = ServerCapabilities::builder()
68///     .enable_logging()
69///     .enable_experimental()
70///     .enable_prompts()
71///     .enable_resources()
72///     .enable_tools()
73///     .enable_tool_list_changed()
74///     .build();
75/// ```
76#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
77#[serde(rename_all = "camelCase")]
78#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
79pub struct ServerCapabilities {
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub experimental: Option<ExperimentalCapabilities>,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub logging: Option<JsonObject>,
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub completions: Option<JsonObject>,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub prompts: Option<PromptsCapability>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub resources: Option<ResourcesCapability>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub tools: Option<ToolsCapability>,
92}
93
94macro_rules! builder {
95    ($Target: ident {$($f: ident: $T: ty),* $(,)?}) => {
96        paste! {
97            #[derive(Default, Clone, Copy, Debug)]
98            pub struct [<$Target BuilderState>]<
99                $(const [<$f:upper>]: bool = false,)*
100            >;
101            #[derive(Debug, Default)]
102            pub struct [<$Target Builder>]<S = [<$Target BuilderState>]> {
103                $(pub $f: Option<$T>,)*
104                pub state: PhantomData<S>
105            }
106            impl $Target {
107                #[doc = "Create a new [`" $Target "`] builder."]
108                pub fn builder() -> [<$Target Builder>] {
109                    <[<$Target Builder>]>::default()
110                }
111            }
112            impl<S> [<$Target Builder>]<S> {
113                pub fn build(self) -> $Target {
114                    $Target {
115                        $( $f: self.$f, )*
116                    }
117                }
118            }
119            impl<S> From<[<$Target Builder>]<S>> for $Target {
120                fn from(builder: [<$Target Builder>]<S>) -> Self {
121                    builder.build()
122                }
123            }
124        }
125        builder!($Target @toggle $($f: $T,) *);
126
127    };
128    ($Target: ident @toggle $f0: ident: $T0: ty, $($f: ident: $T: ty,)*) => {
129        builder!($Target @toggle [][$f0: $T0][$($f: $T,)*]);
130    };
131    ($Target: ident @toggle [$($ff: ident: $Tf: ty,)*][$fn: ident: $TN: ty][$fn_1: ident: $Tn_1: ty, $($ft: ident: $Tt: ty,)*]) => {
132        builder!($Target @impl_toggle [$($ff: $Tf,)*][$fn: $TN][$fn_1: $Tn_1, $($ft:$Tt,)*]);
133        builder!($Target @toggle [$($ff: $Tf,)* $fn: $TN,][$fn_1: $Tn_1][$($ft:$Tt,)*]);
134    };
135    ($Target: ident @toggle [$($ff: ident: $Tf: ty,)*][$fn: ident: $TN: ty][]) => {
136        builder!($Target @impl_toggle [$($ff: $Tf,)*][$fn: $TN][]);
137    };
138    ($Target: ident @impl_toggle [$($ff: ident: $Tf: ty,)*][$fn: ident: $TN: ty][$($ft: ident: $Tt: ty,)*]) => {
139        paste! {
140            impl<
141                $(const [<$ff:upper>]: bool,)*
142                $(const [<$ft:upper>]: bool,)*
143            > [<$Target Builder>]<[<$Target BuilderState>]<
144                $([<$ff:upper>],)*
145                false,
146                $([<$ft:upper>],)*
147            >> {
148                pub fn [<enable_ $fn>](self) -> [<$Target Builder>]<[<$Target BuilderState>]<
149                    $([<$ff:upper>],)*
150                    true,
151                    $([<$ft:upper>],)*
152                >> {
153                    [<$Target Builder>] {
154                        $( $ff: self.$ff, )*
155                        $fn: Some($TN::default()),
156                        $( $ft: self.$ft, )*
157                        state: PhantomData
158                    }
159                }
160                pub fn [<enable_ $fn _with>](self, $fn: $TN) -> [<$Target Builder>]<[<$Target BuilderState>]<
161                    $([<$ff:upper>],)*
162                    true,
163                    $([<$ft:upper>],)*
164                >> {
165                    [<$Target Builder>] {
166                        $( $ff: self.$ff, )*
167                        $fn: Some($fn),
168                        $( $ft: self.$ft, )*
169                        state: PhantomData
170                    }
171                }
172            }
173            // do we really need to disable some thing in builder?
174            // impl<
175            //     $(const [<$ff:upper>]: bool,)*
176            //     $(const [<$ft:upper>]: bool,)*
177            // > [<$Target Builder>]<[<$Target BuilderState>]<
178            //     $([<$ff:upper>],)*
179            //     true,
180            //     $([<$ft:upper>],)*
181            // >> {
182            //     pub fn [<disable_ $fn>](self) -> [<$Target Builder>]<[<$Target BuilderState>]<
183            //         $([<$ff:upper>],)*
184            //         false,
185            //         $([<$ft:upper>],)*
186            //     >> {
187            //         [<$Target Builder>] {
188            //             $( $ff: self.$ff, )*
189            //             $fn: None,
190            //             $( $ft: self.$ft, )*
191            //             state: PhantomData
192            //         }
193            //     }
194            // }
195        }
196    }
197}
198
199builder! {
200    ServerCapabilities {
201        experimental: ExperimentalCapabilities,
202        logging: JsonObject,
203        completions: JsonObject,
204        prompts: PromptsCapability,
205        resources: ResourcesCapability,
206        tools: ToolsCapability
207    }
208}
209
210impl<const E: bool, const L: bool, const C: bool, const P: bool, const R: bool>
211    ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, R, true>>
212{
213    pub fn enable_tool_list_changed(mut self) -> Self {
214        if let Some(c) = self.tools.as_mut() {
215            c.list_changed = Some(true);
216        }
217        self
218    }
219}
220
221impl<const E: bool, const L: bool, const C: bool, const R: bool, const T: bool>
222    ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, true, R, T>>
223{
224    pub fn enable_prompts_list_changed(mut self) -> Self {
225        if let Some(c) = self.prompts.as_mut() {
226            c.list_changed = Some(true);
227        }
228        self
229    }
230}
231
232impl<const E: bool, const L: bool, const C: bool, const P: bool, const T: bool>
233    ServerCapabilitiesBuilder<ServerCapabilitiesBuilderState<E, L, C, P, true, T>>
234{
235    pub fn enable_resources_list_changed(mut self) -> Self {
236        if let Some(c) = self.resources.as_mut() {
237            c.list_changed = Some(true);
238        }
239        self
240    }
241
242    pub fn enable_resources_subscribe(mut self) -> Self {
243        if let Some(c) = self.resources.as_mut() {
244            c.subscribe = Some(true);
245        }
246        self
247    }
248}
249
250builder! {
251    ClientCapabilities{
252        experimental: ExperimentalCapabilities,
253        roots: RootsCapabilities,
254        sampling: JsonObject,
255    }
256}
257
258impl<const E: bool, const S: bool>
259    ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<E, true, S>>
260{
261    pub fn enable_roots_list_changed(mut self) -> Self {
262        if let Some(c) = self.roots.as_mut() {
263            c.list_changed = Some(true);
264        }
265        self
266    }
267}
268
269#[cfg(test)]
270mod test {
271    use super::*;
272    #[test]
273    fn test_builder() {
274        let builder = <ServerCapabilitiesBuilder>::default()
275            .enable_logging()
276            .enable_experimental()
277            .enable_prompts()
278            .enable_resources()
279            .enable_tools()
280            .enable_tool_list_changed();
281        assert_eq!(builder.logging, Some(JsonObject::default()));
282        assert_eq!(builder.prompts, Some(PromptsCapability::default()));
283        assert_eq!(builder.resources, Some(ResourcesCapability::default()));
284        assert_eq!(
285            builder.tools,
286            Some(ToolsCapability {
287                list_changed: Some(true),
288            })
289        );
290        assert_eq!(
291            builder.experimental,
292            Some(ExperimentalCapabilities::default())
293        );
294        let client_builder = <ClientCapabilitiesBuilder>::default()
295            .enable_experimental()
296            .enable_roots()
297            .enable_roots_list_changed()
298            .enable_sampling();
299        assert_eq!(
300            client_builder.experimental,
301            Some(ExperimentalCapabilities::default())
302        );
303        assert_eq!(
304            client_builder.roots,
305            Some(RootsCapabilities {
306                list_changed: Some(true),
307            })
308        );
309    }
310}