mcpx/protocol/
resources.rs

1//! Resource types for the MCP protocol
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6use super::{Annotations, Request, Notification};
7use super::messages::MessageResult;
8
9/// A known resource that the server is capable of reading.
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub struct Resource {
12    /// The URI of this resource.
13    pub uri: String,
14
15    /// A human-readable name for this resource.
16    pub name: String,
17
18    /// A description of what this resource represents.
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub description: Option<String>,
21
22    /// The MIME type of this resource, if known.
23    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
24    pub mime_type: Option<String>,
25
26    /// Optional annotations for the client.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub annotations: Option<Annotations>,
29
30    /// The size of the raw resource content in bytes, if known.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub size: Option<i64>,
33}
34
35impl Resource {
36    /// Create a new resource
37    pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
38        Self {
39            uri: uri.into(),
40            name: name.into(),
41            description: None,
42            mime_type: None,
43            annotations: None,
44            size: None,
45        }
46    }
47
48    /// Create a new resource with description
49    pub fn with_description(uri: impl Into<String>, name: impl Into<String>, description: impl Into<String>) -> Self {
50        Self {
51            uri: uri.into(),
52            name: name.into(),
53            description: Some(description.into()),
54            mime_type: None,
55            annotations: None,
56            size: None,
57        }
58    }
59
60    /// Set the MIME type for the resource
61    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
62        self.mime_type = Some(mime_type.into());
63        self
64    }
65
66    /// Set the annotations for the resource
67    pub fn with_annotations(mut self, annotations: Annotations) -> Self {
68        self.annotations = Some(annotations);
69        self
70    }
71
72    /// Set the size for the resource
73    pub fn with_size(mut self, size: i64) -> Self {
74        self.size = Some(size);
75        self
76    }
77}
78
79impl Default for Resource {
80    fn default() -> Self {
81        Self {
82            uri: String::new(),
83            name: String::new(),
84            description: None,
85            mime_type: None,
86            annotations: None,
87            size: None,
88        }
89    }
90}
91
92/// A template description for resources available on the server.
93#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
94pub struct ResourceTemplate {
95    /// A URI template that can be used to construct resource URIs.
96    #[serde(rename = "uriTemplate")]
97    pub uri_template: String,
98
99    /// A human-readable name for the type of resource this template refers to.
100    pub name: String,
101
102    /// A description of what this template is for.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub description: Option<String>,
105
106    /// The MIME type for all resources that match this template.
107    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
108    pub mime_type: Option<String>,
109
110    /// Optional annotations for the client.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub annotations: Option<Annotations>,
113}
114
115impl ResourceTemplate {
116    /// Create a new resource template
117    pub fn new(uri_template: impl Into<String>, name: impl Into<String>) -> Self {
118        Self {
119            uri_template: uri_template.into(),
120            name: name.into(),
121            description: None,
122            mime_type: None,
123            annotations: None,
124        }
125    }
126
127    /// Create a new resource template with description
128    pub fn with_description(
129        uri_template: impl Into<String>,
130        name: impl Into<String>,
131        description: impl Into<String>,
132    ) -> Self {
133        Self {
134            uri_template: uri_template.into(),
135            name: name.into(),
136            description: Some(description.into()),
137            mime_type: None,
138            annotations: None,
139        }
140    }
141
142    /// Set the MIME type for the resource template
143    pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
144        self.mime_type = Some(mime_type.into());
145        self
146    }
147
148    /// Set the annotations for the resource template
149    pub fn with_annotations(mut self, annotations: Annotations) -> Self {
150        self.annotations = Some(annotations);
151        self
152    }
153}
154
155/// The contents of a specific resource or sub-resource.
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
157pub struct ResourceContents {
158    /// The URI of this resource.
159    pub uri: String,
160
161    /// The MIME type of this resource, if known.
162    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
163    pub mime_type: Option<String>,
164}
165
166impl ResourceContents {
167    /// Create new resource contents
168    pub fn new(uri: impl Into<String>) -> Self {
169        Self {
170            uri: uri.into(),
171            mime_type: None,
172        }
173    }
174
175    /// Create new resource contents with MIME type
176    pub fn with_mime_type(uri: impl Into<String>, mime_type: impl Into<String>) -> Self {
177        Self {
178            uri: uri.into(),
179            mime_type: Some(mime_type.into()),
180        }
181    }
182}
183
184/// Text contents of a resource
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
186pub struct TextResourceContents {
187    /// The URI of this resource.
188    pub uri: String,
189
190    /// The MIME type of this resource, if known.
191    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
192    pub mime_type: Option<String>,
193
194    /// The text of the item.
195    pub text: String,
196}
197
198impl TextResourceContents {
199    /// Create new text resource contents
200    pub fn new(uri: impl Into<String>, text: impl Into<String>) -> Self {
201        Self {
202            uri: uri.into(),
203            mime_type: None,
204            text: text.into(),
205        }
206    }
207
208    /// Create new text resource contents with MIME type
209    pub fn with_mime_type(uri: impl Into<String>, text: impl Into<String>, mime_type: impl Into<String>) -> Self {
210        Self {
211            uri: uri.into(),
212            mime_type: Some(mime_type.into()),
213            text: text.into(),
214        }
215    }
216}
217
218/// Binary contents of a resource
219#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
220pub struct BlobResourceContents {
221    /// The URI of this resource.
222    pub uri: String,
223
224    /// The MIME type of this resource, if known.
225    #[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
226    pub mime_type: Option<String>,
227
228    /// The base64-encoded binary data of the item.
229    pub blob: String,
230}
231
232impl BlobResourceContents {
233    /// Create new blob resource contents
234    pub fn new(uri: impl Into<String>, blob: impl Into<String>) -> Self {
235        Self {
236            uri: uri.into(),
237            mime_type: None,
238            blob: blob.into(),
239        }
240    }
241
242    /// Create new blob resource contents with MIME type
243    pub fn with_mime_type(uri: impl Into<String>, blob: impl Into<String>, mime_type: impl Into<String>) -> Self {
244        Self {
245            uri: uri.into(),
246            mime_type: Some(mime_type.into()),
247            blob: blob.into(),
248        }
249    }
250}
251
252/// Request to list resources
253#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
254pub struct ListResourcesRequest {
255    /// Method is always "resources/list"
256    pub method: String,
257
258    /// Optional request parameters
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub params: Option<ListResourcesParams>,
261}
262
263impl Request for ListResourcesRequest {
264    const METHOD: &'static str = "resources/list";
265
266    fn method(&self) -> &str {
267        &self.method
268    }
269
270    fn params(&self) -> Option<&serde_json::Value> {
271        None
272    }
273}
274
275impl ListResourcesRequest {
276    /// Create a new list resources request
277    pub fn new() -> Self {
278        Self {
279            method: Self::METHOD.to_string(),
280            params: None,
281        }
282    }
283
284    /// Create a new list resources request with a cursor
285    pub fn with_cursor(cursor: impl Into<String>) -> Self {
286        Self {
287            method: Self::METHOD.to_string(),
288            params: Some(ListResourcesParams {
289                cursor: Some(cursor.into()),
290            }),
291        }
292    }
293}
294
295/// Parameters for list resources request
296#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
297pub struct ListResourcesParams {
298    /// An opaque token representing the current pagination position.
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub cursor: Option<String>,
301}
302
303/// Result of listing resources
304#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
305pub struct ListResourcesResult {
306    /// The list of resources
307    pub resources: Vec<Resource>,
308
309    /// An opaque token representing the pagination position after the last returned result.
310    #[serde(rename = "nextCursor", skip_serializing_if = "Option::is_none")]
311    pub next_cursor: Option<String>,
312
313    /// Metadata for the result
314    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
315    pub meta: Option<super::messages::ResultMeta>,
316}
317
318impl MessageResult for ListResourcesResult {}
319
320impl ListResourcesResult {
321    /// Create a new list resources result
322    pub fn new(resources: Vec<Resource>) -> Self {
323        Self {
324            resources,
325            next_cursor: None,
326            meta: None,
327        }
328    }
329
330    /// Create a new list resources result with a next cursor
331    pub fn with_next_cursor(resources: Vec<Resource>, next_cursor: impl Into<String>) -> Self {
332        Self {
333            resources,
334            next_cursor: Some(next_cursor.into()),
335            meta: None,
336        }
337    }
338}
339
340/// Request to read a resource
341#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
342pub struct ReadResourceRequest {
343    /// Method is always "resources/read"
344    pub method: String,
345
346    /// Request parameters
347    pub params: ReadResourceParams,
348}
349
350impl Request for ReadResourceRequest {
351    const METHOD: &'static str = "resources/read";
352
353    fn method(&self) -> &str {
354        &self.method
355    }
356
357    fn params(&self) -> Option<&serde_json::Value> {
358        None
359    }
360}
361
362impl ReadResourceRequest {
363    /// Create a new read resource request
364    pub fn new(uri: impl Into<String>) -> Self {
365        Self {
366            method: Self::METHOD.to_string(),
367            params: ReadResourceParams {
368                uri: uri.into(),
369            },
370        }
371    }
372}
373
374/// Parameters for read resource request
375#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
376pub struct ReadResourceParams {
377    /// The URI of the resource to read.
378    pub uri: String,
379}
380
381/// Result of reading a resource
382#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
383pub struct ReadResourceResult {
384    /// The contents of the resource
385    pub contents: Vec<ResourceContent>,
386
387    /// Metadata for the result
388    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
389    pub meta: Option<super::messages::ResultMeta>,
390}
391
392impl ReadResourceResult {
393    /// Create a new read resource result
394    pub fn new(contents: Vec<ResourceContent>) -> Self {
395        Self {
396            contents,
397            meta: None,
398        }
399    }
400}
401
402/// Content of a resource, either text or binary
403#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
404#[serde(untagged)]
405pub enum ResourceContent {
406    /// Text content
407    Text(TextResourceContents),
408    /// Binary content
409    Blob(BlobResourceContents),
410}
411
412/// Request to subscribe to resource updates
413#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
414pub struct SubscribeRequest {
415    /// Method is always "resources/subscribe"
416    pub method: String,
417
418    /// Request parameters
419    pub params: SubscribeParams,
420}
421
422impl Request for SubscribeRequest {
423    const METHOD: &'static str = "resources/subscribe";
424
425    fn method(&self) -> &str {
426        &self.method
427    }
428
429    fn params(&self) -> Option<&serde_json::Value> {
430        None
431    }
432}
433
434impl SubscribeRequest {
435    /// Create a new subscribe request
436    pub fn new(uri: impl Into<String>) -> Self {
437        Self {
438            method: Self::METHOD.to_string(),
439            params: SubscribeParams {
440                uri: uri.into(),
441            },
442        }
443    }
444}
445
446/// Parameters for subscribe request
447#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
448pub struct SubscribeParams {
449    /// The URI of the resource to subscribe to.
450    pub uri: String,
451}
452
453/// Request to unsubscribe from resource updates
454#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
455pub struct UnsubscribeRequest {
456    /// Method is always "resources/unsubscribe"
457    pub method: String,
458
459    /// Request parameters
460    pub params: UnsubscribeParams,
461}
462
463impl Request for UnsubscribeRequest {
464    const METHOD: &'static str = "resources/unsubscribe";
465
466    fn method(&self) -> &str {
467        &self.method
468    }
469
470    fn params(&self) -> Option<&serde_json::Value> {
471        None
472    }
473}
474
475impl UnsubscribeRequest {
476    /// Create a new unsubscribe request
477    pub fn new(uri: impl Into<String>) -> Self {
478        Self {
479            method: Self::METHOD.to_string(),
480            params: UnsubscribeParams {
481                uri: uri.into(),
482            },
483        }
484    }
485}
486
487/// Parameters for unsubscribe request
488#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
489pub struct UnsubscribeParams {
490    /// The URI of the resource to unsubscribe from.
491    pub uri: String,
492}
493
494/// Notification that a resource has been updated
495#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
496pub struct ResourceUpdatedNotification {
497    /// Method is always "notifications/resources/updated"
498    pub method: String,
499
500    /// Notification parameters
501    pub params: ResourceUpdatedParams,
502}
503
504impl Notification for ResourceUpdatedNotification {
505    const METHOD: &'static str = "notifications/resources/updated";
506
507    fn method(&self) -> &str {
508        &self.method
509    }
510
511    fn params(&self) -> Option<&serde_json::Value> {
512        None
513    }
514}
515
516impl ResourceUpdatedNotification {
517    /// Create a new resource updated notification
518    pub fn new(uri: impl Into<String>) -> Self {
519        Self {
520            method: "notifications/resources/updated".to_string(),
521            params: ResourceUpdatedParams {
522                uri: uri.into(),
523            },
524        }
525    }
526}
527
528/// Parameters for resource updated notification
529#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
530pub struct ResourceUpdatedParams {
531    /// The URI of the resource that has been updated.
532    pub uri: String,
533}
534
535/// Notification that the list of resources has changed
536#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
537pub struct ResourceListChangedNotification {
538    /// Method is always "notifications/resources/list_changed"
539    pub method: String,
540
541    /// Optional notification parameters
542    #[serde(skip_serializing_if = "Option::is_none")]
543    pub params: Option<super::messages::NotificationParams>,
544}
545
546impl ResourceListChangedNotification {
547    /// Create a new resource list changed notification
548    pub fn new() -> Self {
549        Self {
550            method: "notifications/resources/list_changed".to_string(),
551            params: None,
552        }
553    }
554}
555
556/// The contents of a resource, embedded into a prompt or tool call result.
557#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
558pub struct EmbeddedResource {
559    /// Type is always "resource"
560    pub r#type: String,
561
562    /// The resource content
563    pub resource: ResourceContent,
564
565    /// Optional annotations for the client.
566    #[serde(skip_serializing_if = "Option::is_none")]
567    pub annotations: Option<Annotations>,
568}
569
570impl EmbeddedResource {
571    /// Create a new embedded resource with text content
572    pub fn text(uri: impl Into<String>, text: impl Into<String>) -> Self {
573        Self {
574            r#type: "resource".to_string(),
575            resource: ResourceContent::Text(TextResourceContents::new(uri, text)),
576            annotations: None,
577        }
578    }
579
580    /// Create a new embedded resource with binary content
581    pub fn blob(uri: impl Into<String>, blob: impl Into<String>) -> Self {
582        Self {
583            r#type: "resource".to_string(),
584            resource: ResourceContent::Blob(BlobResourceContents::new(uri, blob)),
585            annotations: None,
586        }
587    }
588
589    /// Set the annotations for the embedded resource
590    pub fn with_annotations(mut self, annotations: Annotations) -> Self {
591        self.annotations = Some(annotations);
592        self
593    }
594}
595
596/// A reference to a resource or resource template definition.
597#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
598pub struct ResourceReference {
599    /// Type is always "ref/resource"
600    pub r#type: String,
601
602    /// The URI or URI template of the resource.
603    pub uri: String,
604}
605
606impl ResourceReference {
607    /// Create a new resource reference
608    pub fn new(uri: impl Into<String>) -> Self {
609        Self {
610            r#type: "ref/resource".to_string(),
611            uri: uri.into(),
612        }
613    }
614}