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#[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#[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 }
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}