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(uri: impl Into<String>, data: impl Into<String>, mime_type: impl Into<String>) -> Self {
143 Self {
144 uri: uri.into(),
145 mime_type: Some(mime_type.into()),
146 text: None,
147 blob: Some(data.into()),
148 }
149 }
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
154pub struct ResourceTemplate {
155 pub uri_template: String,
157 pub name: String,
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub title: Option<String>,
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub description: Option<String>,
165 #[serde(skip_serializing_if = "Option::is_none")]
167 pub mime_type: Option<String>,
168}
169
170impl ResourceTemplate {
171 pub fn new(uri_template: impl Into<String>, name: impl Into<String>) -> Self {
173 Self {
174 uri_template: uri_template.into(),
175 name: name.into(),
176 title: None,
177 description: None,
178 mime_type: None,
179 }
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
185pub struct Prompt {
186 pub name: String,
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub title: Option<String>,
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub description: Option<String>,
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub arguments: Option<Vec<PromptArgument>>,
197}
198
199impl Prompt {
200 pub fn new(name: impl Into<String>) -> Self {
202 Self {
203 name: name.into(),
204 title: None,
205 description: None,
206 arguments: None,
207 }
208 }
209
210 pub fn builder(name: impl Into<String>) -> PromptBuilder {
212 PromptBuilder::new(name)
213 }
214}
215
216#[derive(Debug)]
218pub struct PromptBuilder {
219 prompt: Prompt,
220}
221
222impl PromptBuilder {
223 pub fn new(name: impl Into<String>) -> Self {
225 Self {
226 prompt: Prompt::new(name),
227 }
228 }
229
230 pub fn title(mut self, title: impl Into<String>) -> Self {
232 self.prompt.title = Some(title.into());
233 self
234 }
235
236 pub fn description(mut self, description: impl Into<String>) -> Self {
238 self.prompt.description = Some(description.into());
239 self
240 }
241
242 pub fn argument(mut self, arg: PromptArgument) -> Self {
244 self.prompt
245 .arguments
246 .get_or_insert_with(Vec::new)
247 .push(arg);
248 self
249 }
250
251 pub fn build(self) -> Prompt {
253 self.prompt
254 }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
259pub struct PromptArgument {
260 pub name: String,
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub description: Option<String>,
265 #[serde(skip_serializing_if = "Option::is_none")]
267 pub required: Option<bool>,
268}
269
270impl PromptArgument {
271 pub fn new(name: impl Into<String>) -> Self {
273 Self {
274 name: name.into(),
275 description: None,
276 required: None,
277 }
278 }
279
280 pub fn required(name: impl Into<String>, description: impl Into<String>) -> Self {
282 Self {
283 name: name.into(),
284 description: Some(description.into()),
285 required: Some(true),
286 }
287 }
288
289 pub fn optional(name: impl Into<String>, description: impl Into<String>) -> Self {
291 Self {
292 name: name.into(),
293 description: Some(description.into()),
294 required: Some(false),
295 }
296 }
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
301pub struct PromptMessage {
302 pub role: String,
304 pub content: PromptContent,
306}
307
308impl PromptMessage {
309 pub fn user_text(text: impl Into<String>) -> Self {
311 Self {
312 role: "user".to_string(),
313 content: PromptContent::Text {
314 r#type: "text".to_string(),
315 text: text.into(),
316 },
317 }
318 }
319
320 pub fn assistant_text(text: impl Into<String>) -> Self {
322 Self {
323 role: "assistant".to_string(),
324 content: PromptContent::Text {
325 r#type: "text".to_string(),
326 text: text.into(),
327 },
328 }
329 }
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
334#[serde(untagged)]
335pub enum PromptContent {
336 Text {
338 r#type: String,
339 text: String,
340 },
341 Image {
343 r#type: String,
344 data: String,
345 mime_type: String,
346 },
347 Audio {
349 r#type: String,
350 data: String,
351 mime_type: String,
352 },
353 Resource {
355 r#type: String,
356 resource: EmbeddedResource,
357 },
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
362pub struct EmbeddedResource {
363 pub uri: String,
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub mime_type: Option<String>,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub text: Option<String>,
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub blob: Option<String>,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
378pub struct GetPromptResult {
379 #[serde(skip_serializing_if = "Option::is_none")]
381 pub description: Option<String>,
382 pub messages: Vec<PromptMessage>,
384}
385
386impl GetPromptResult {
387 pub fn new(messages: Vec<PromptMessage>) -> Self {
389 Self {
390 description: None,
391 messages,
392 }
393 }
394
395 pub fn with_description(description: impl Into<String>, messages: Vec<PromptMessage>) -> Self {
397 Self {
398 description: Some(description.into()),
399 messages,
400 }
401 }
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize, Default)]
406pub struct ResourceListResult {
407 pub resources: Vec<Resource>,
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub next_cursor: Option<String>,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize, Default)]
416pub struct PromptListResult {
417 pub prompts: Vec<Prompt>,
419 #[serde(skip_serializing_if = "Option::is_none")]
421 pub next_cursor: Option<String>,
422}
423
424#[derive(Debug, Clone, Serialize, Deserialize, Default)]
426pub struct ResourceTemplateListResult {
427 pub resource_templates: Vec<ResourceTemplate>,
429 #[serde(skip_serializing_if = "Option::is_none")]
431 pub next_cursor: Option<String>,
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 #[test]
439 fn test_resource_creation() {
440 let resource = Resource::new("file:///path/to/file.txt", "file.txt");
441 assert_eq!(resource.uri, "file:///path/to/file.txt");
442 assert_eq!(resource.name, "file.txt");
443 }
444
445 #[test]
446 fn test_resource_builder() {
447 let resource = Resource::builder("file:///test.md", "test.md")
448 .title("Test File")
449 .description("A test markdown file")
450 .mime_type("text/markdown")
451 .size(1024)
452 .build();
453
454 assert_eq!(resource.title, Some("Test File".to_string()));
455 assert_eq!(resource.size, Some(1024));
456 }
457
458 #[test]
459 fn test_resource_contents_text() {
460 let contents = ResourceContents::text("file:///test.txt", "Hello, World!");
461 assert!(contents.text.is_some());
462 assert!(contents.blob.is_none());
463 }
464
465 #[test]
466 fn test_resource_contents_blob() {
467 let contents = ResourceContents::blob("file:///image.png", "base64data", "image/png");
468 assert!(contents.blob.is_some());
469 assert!(contents.text.is_none());
470 }
471
472 #[test]
473 fn test_prompt_creation() {
474 let prompt = Prompt::new("code_review");
475 assert_eq!(prompt.name, "code_review");
476 }
477
478 #[test]
479 fn test_prompt_builder() {
480 let prompt = Prompt::builder("code_review")
481 .title("Code Review")
482 .description("Review code for best practices")
483 .argument(PromptArgument::required("code", "Code to review"))
484 .argument(PromptArgument::optional("language", "Programming language"))
485 .build();
486
487 assert_eq!(prompt.title, Some("Code Review".to_string()));
488 assert_eq!(prompt.arguments.as_ref().unwrap().len(), 2);
489 }
490
491 #[test]
492 fn test_prompt_message() {
493 let user_msg = PromptMessage::user_text("Please review this code");
494 assert_eq!(user_msg.role, "user");
495
496 let asst_msg = PromptMessage::assistant_text("I'll review the code");
497 assert_eq!(asst_msg.role, "assistant");
498 }
499
500 #[test]
501 fn test_get_prompt_result() {
502 let result = GetPromptResult::with_description(
503 "Code review prompt",
504 vec![PromptMessage::user_text("Review this")],
505 );
506 assert_eq!(result.description, Some("Code review prompt".to_string()));
507 assert_eq!(result.messages.len(), 1);
508 }
509
510 #[test]
511 fn test_resource_serialization() {
512 let resource = Resource::builder("file:///test.txt", "test.txt")
513 .mime_type("text/plain")
514 .build();
515
516 let json = serde_json::to_string(&resource).unwrap();
517 let parsed: Resource = serde_json::from_str(&json).unwrap();
518 assert_eq!(resource, parsed);
519 }
520
521 #[test]
522 fn test_prompt_serialization() {
523 let prompt = Prompt::builder("test")
524 .description("Test prompt")
525 .argument(PromptArgument::required("input", "Input text"))
526 .build();
527
528 let json = serde_json::to_string(&prompt).unwrap();
529 let parsed: Prompt = serde_json::from_str(&json).unwrap();
530 assert_eq!(prompt, parsed);
531 }
532}