1use serde::{Deserialize, Serialize};
6#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct Resource {
9 pub uri: String,
11 pub name: String,
13 #[serde(skip_serializing_if = "Option::is_none")]
15 pub title: Option<String>,
16 #[serde(skip_serializing_if = "Option::is_none")]
18 pub description: Option<String>,
19 #[serde(skip_serializing_if = "Option::is_none")]
21 pub mime_type: Option<String>,
22 #[serde(skip_serializing_if = "Option::is_none")]
24 pub size: Option<u64>,
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub annotations: Option<ResourceAnnotations>,
28}
29
30impl Resource {
31 pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
33 Self {
34 uri: uri.into(),
35 name: name.into(),
36 title: None,
37 description: None,
38 mime_type: None,
39 size: None,
40 annotations: None,
41 }
42 }
43
44 pub fn builder(uri: impl Into<String>, name: impl Into<String>) -> ResourceBuilder {
46 ResourceBuilder::new(uri, name)
47 }
48}
49
50#[derive(Debug)]
52pub struct ResourceBuilder {
53 resource: Resource,
54}
55
56impl ResourceBuilder {
57 pub fn new(uri: impl Into<String>, name: impl Into<String>) -> Self {
59 Self {
60 resource: Resource::new(uri, name),
61 }
62 }
63
64 pub fn title(mut self, title: impl Into<String>) -> Self {
66 self.resource.title = Some(title.into());
67 self
68 }
69
70 pub fn description(mut self, description: impl Into<String>) -> Self {
72 self.resource.description = Some(description.into());
73 self
74 }
75
76 pub fn mime_type(mut self, mime_type: impl Into<String>) -> Self {
78 self.resource.mime_type = Some(mime_type.into());
79 self
80 }
81
82 pub fn size(mut self, size: u64) -> Self {
84 self.resource.size = Some(size);
85 self
86 }
87
88 pub fn annotations(mut self, annotations: ResourceAnnotations) -> Self {
90 self.resource.annotations = Some(annotations);
91 self
92 }
93
94 pub fn build(self) -> Resource {
96 self.resource
97 }
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
102pub struct ResourceAnnotations {
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub audience: Option<Vec<String>>,
106 #[serde(skip_serializing_if = "Option::is_none")]
108 pub priority: Option<f64>,
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub last_modified: Option<String>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
116pub struct ResourceContents {
117 pub uri: String,
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub mime_type: Option<String>,
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub text: Option<String>,
125 #[serde(skip_serializing_if = "Option::is_none")]
127 pub blob: Option<String>,
128}
129
130impl ResourceContents {
131 pub fn text(uri: impl Into<String>, content: impl Into<String>) -> Self {
133 Self {
134 uri: uri.into(),
135 mime_type: Some("text/plain".to_string()),
136 text: Some(content.into()),
137 blob: None,
138 }
139 }
140
141 pub fn blob(
143 uri: impl Into<String>,
144 data: impl Into<String>,
145 mime_type: impl Into<String>,
146 ) -> Self {
147 Self {
148 uri: uri.into(),
149 mime_type: Some(mime_type.into()),
150 text: None,
151 blob: Some(data.into()),
152 }
153 }
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
158pub struct ResourceTemplate {
159 pub uri_template: String,
161 pub name: String,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub title: Option<String>,
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub description: Option<String>,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub mime_type: Option<String>,
172}
173
174impl ResourceTemplate {
175 pub fn new(uri_template: impl Into<String>, name: impl Into<String>) -> Self {
177 Self {
178 uri_template: uri_template.into(),
179 name: name.into(),
180 title: None,
181 description: None,
182 mime_type: None,
183 }
184 }
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
189pub struct Prompt {
190 pub name: String,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub title: Option<String>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub description: Option<String>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub arguments: Option<Vec<PromptArgument>>,
201}
202
203impl Prompt {
204 pub fn new(name: impl Into<String>) -> Self {
206 Self {
207 name: name.into(),
208 title: None,
209 description: None,
210 arguments: None,
211 }
212 }
213
214 pub fn builder(name: impl Into<String>) -> PromptBuilder {
216 PromptBuilder::new(name)
217 }
218}
219
220#[derive(Debug)]
222pub struct PromptBuilder {
223 prompt: Prompt,
224}
225
226impl PromptBuilder {
227 pub fn new(name: impl Into<String>) -> Self {
229 Self {
230 prompt: Prompt::new(name),
231 }
232 }
233
234 pub fn title(mut self, title: impl Into<String>) -> Self {
236 self.prompt.title = Some(title.into());
237 self
238 }
239
240 pub fn description(mut self, description: impl Into<String>) -> Self {
242 self.prompt.description = Some(description.into());
243 self
244 }
245
246 pub fn argument(mut self, arg: PromptArgument) -> Self {
248 self.prompt.arguments.get_or_insert_with(Vec::new).push(arg);
249 self
250 }
251
252 pub fn build(self) -> Prompt {
254 self.prompt
255 }
256}
257
258#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
260pub struct PromptArgument {
261 pub name: String,
263 #[serde(skip_serializing_if = "Option::is_none")]
265 pub description: Option<String>,
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub required: Option<bool>,
269}
270
271impl PromptArgument {
272 pub fn new(name: impl Into<String>) -> Self {
274 Self {
275 name: name.into(),
276 description: None,
277 required: None,
278 }
279 }
280
281 pub fn required(name: impl Into<String>, description: impl Into<String>) -> Self {
283 Self {
284 name: name.into(),
285 description: Some(description.into()),
286 required: Some(true),
287 }
288 }
289
290 pub fn optional(name: impl Into<String>, description: impl Into<String>) -> Self {
292 Self {
293 name: name.into(),
294 description: Some(description.into()),
295 required: Some(false),
296 }
297 }
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
302pub struct PromptMessage {
303 pub role: String,
305 pub content: PromptContent,
307}
308
309impl PromptMessage {
310 pub fn user_text(text: impl Into<String>) -> Self {
312 Self {
313 role: "user".to_string(),
314 content: PromptContent::Text {
315 r#type: "text".to_string(),
316 text: text.into(),
317 },
318 }
319 }
320
321 pub fn assistant_text(text: impl Into<String>) -> Self {
323 Self {
324 role: "assistant".to_string(),
325 content: PromptContent::Text {
326 r#type: "text".to_string(),
327 text: text.into(),
328 },
329 }
330 }
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
335#[serde(untagged)]
336pub enum PromptContent {
337 Text { r#type: String, text: String },
339 Image {
341 r#type: String,
342 data: String,
343 mime_type: String,
344 },
345 Audio {
347 r#type: String,
348 data: String,
349 mime_type: String,
350 },
351 Resource {
353 r#type: String,
354 resource: EmbeddedResource,
355 },
356}
357
358#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
360pub struct EmbeddedResource {
361 pub uri: String,
363 #[serde(skip_serializing_if = "Option::is_none")]
365 pub mime_type: Option<String>,
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub text: Option<String>,
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub blob: Option<String>,
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
376pub struct GetPromptResult {
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub description: Option<String>,
380 pub messages: Vec<PromptMessage>,
382}
383
384impl GetPromptResult {
385 pub fn new(messages: Vec<PromptMessage>) -> Self {
387 Self {
388 description: None,
389 messages,
390 }
391 }
392
393 pub fn with_description(description: impl Into<String>, messages: Vec<PromptMessage>) -> Self {
395 Self {
396 description: Some(description.into()),
397 messages,
398 }
399 }
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize, Default)]
404pub struct ResourceListResult {
405 pub resources: Vec<Resource>,
407 #[serde(skip_serializing_if = "Option::is_none")]
409 pub next_cursor: Option<String>,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize, Default)]
414pub struct PromptListResult {
415 pub prompts: Vec<Prompt>,
417 #[serde(skip_serializing_if = "Option::is_none")]
419 pub next_cursor: Option<String>,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize, Default)]
424pub struct ResourceTemplateListResult {
425 pub resource_templates: Vec<ResourceTemplate>,
427 #[serde(skip_serializing_if = "Option::is_none")]
429 pub next_cursor: Option<String>,
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_resource_creation() {
438 let resource = Resource::new("file:///path/to/file.txt", "file.txt");
439 assert_eq!(resource.uri, "file:///path/to/file.txt");
440 assert_eq!(resource.name, "file.txt");
441 }
442
443 #[test]
444 fn test_resource_builder() {
445 let resource = Resource::builder("file:///test.md", "test.md")
446 .title("Test File")
447 .description("A test markdown file")
448 .mime_type("text/markdown")
449 .size(1024)
450 .build();
451
452 assert_eq!(resource.title, Some("Test File".to_string()));
453 assert_eq!(resource.size, Some(1024));
454 }
455
456 #[test]
457 fn test_resource_contents_text() {
458 let contents = ResourceContents::text("file:///test.txt", "Hello, World!");
459 assert!(contents.text.is_some());
460 assert!(contents.blob.is_none());
461 }
462
463 #[test]
464 fn test_resource_contents_blob() {
465 let contents = ResourceContents::blob("file:///image.png", "base64data", "image/png");
466 assert!(contents.blob.is_some());
467 assert!(contents.text.is_none());
468 }
469
470 #[test]
471 fn test_prompt_creation() {
472 let prompt = Prompt::new("code_review");
473 assert_eq!(prompt.name, "code_review");
474 }
475
476 #[test]
477 fn test_prompt_builder() {
478 let prompt = Prompt::builder("code_review")
479 .title("Code Review")
480 .description("Review code for best practices")
481 .argument(PromptArgument::required("code", "Code to review"))
482 .argument(PromptArgument::optional("language", "Programming language"))
483 .build();
484
485 assert_eq!(prompt.title, Some("Code Review".to_string()));
486 assert_eq!(prompt.arguments.as_ref().unwrap().len(), 2);
487 }
488
489 #[test]
490 fn test_prompt_message() {
491 let user_msg = PromptMessage::user_text("Please review this code");
492 assert_eq!(user_msg.role, "user");
493
494 let asst_msg = PromptMessage::assistant_text("I'll review the code");
495 assert_eq!(asst_msg.role, "assistant");
496 }
497
498 #[test]
499 fn test_get_prompt_result() {
500 let result = GetPromptResult::with_description(
501 "Code review prompt",
502 vec![PromptMessage::user_text("Review this")],
503 );
504 assert_eq!(result.description, Some("Code review prompt".to_string()));
505 assert_eq!(result.messages.len(), 1);
506 }
507
508 #[test]
509 fn test_resource_serialization() {
510 let resource = Resource::builder("file:///test.txt", "test.txt")
511 .mime_type("text/plain")
512 .build();
513
514 let json = serde_json::to_string(&resource).unwrap();
515 let parsed: Resource = serde_json::from_str(&json).unwrap();
516 assert_eq!(resource, parsed);
517 }
518
519 #[test]
520 fn test_prompt_serialization() {
521 let prompt = Prompt::builder("test")
522 .description("Test prompt")
523 .argument(PromptArgument::required("input", "Input text"))
524 .build();
525
526 let json = serde_json::to_string(&prompt).unwrap();
527 let parsed: Prompt = serde_json::from_str(&json).unwrap();
528 assert_eq!(prompt, parsed);
529 }
530}