Skip to main content

sovran_mcp/
types.rs

1use crate::messaging::{LogLevel, NotificationMethod};
2use crate::McpError;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6use std::fmt;
7use url::Url;
8
9pub const LATEST_PROTOCOL_VERSION: &str = "2024-11-05";
10
11//
12// Core Protocol Types
13// Basic types used throughout the MCP implementation
14//
15
16#[derive(Debug, Clone, Serialize, Deserialize, Default)]
17#[serde(rename_all = "camelCase")]
18pub struct Implementation {
19    pub name: String,
20    pub version: String,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
24#[serde(rename_all = "lowercase")]
25pub enum Role {
26    System,
27    User,
28    Assistant,
29}
30
31impl fmt::Display for Role {
32    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33        match self {
34            Role::System => write!(f, "System"),
35            Role::User => write!(f, "User"),
36            Role::Assistant => write!(f, "Assistant"),
37        }
38    }
39}
40
41//
42// Initialization & Capabilities
43// Types used during protocol initialization and capability negotiation
44//
45
46#[derive(Debug, Clone, Serialize, Deserialize, Default)]
47#[serde(rename_all = "camelCase")]
48pub struct InitializeRequest {
49    pub protocol_version: String,
50    pub capabilities: ClientCapabilities,
51    pub client_info: Implementation,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize, Default)]
55#[serde(rename_all = "camelCase")]
56pub struct InitializeResponse {
57    pub protocol_version: String,
58    pub capabilities: ServerCapabilities,
59    pub server_info: Implementation,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, Default)]
63#[serde(rename_all = "camelCase")]
64pub struct ServerCapabilities {
65    #[serde(skip_serializing_if = "Option::is_none")]
66    pub tools: Option<serde_json::Value>,
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub experimental: Option<serde_json::Value>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub logging: Option<serde_json::Value>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub prompts: Option<PromptCapabilities>,
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub resources: Option<ResourceCapabilities>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, Default)]
78#[serde(rename_all = "camelCase")]
79pub struct ClientCapabilities {
80    #[serde(default)]
81    pub experimental: HashMap<String, serde_json::Value>,
82    #[serde(default)]
83    pub sampling: HashMap<String, serde_json::Value>,
84    #[serde(default)]
85    pub roots: RootCapabilities,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, Default)]
89#[serde(rename_all = "camelCase")]
90pub struct RootCapabilities {
91    #[serde(default)]
92    pub list_changed: bool,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, Default)]
96#[serde(rename_all = "camelCase")]
97pub struct PromptCapabilities {
98    pub list_changed: Option<bool>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, Default)]
102#[serde(rename_all = "camelCase")]
103pub struct ResourceCapabilities {
104    pub subscribe: Option<bool>,
105    pub list_changed: Option<bool>,
106}
107
108//
109// Message Content Types
110// Types representing different kinds of message content
111//
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(tag = "type")]
115pub enum MessageContent {
116    #[serde(rename = "text")]
117    Text(TextContent),
118    #[serde(rename = "image")]
119    Image(ImageContent),
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct TextContent {
124    pub text: String,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ImageContent {
129    pub data: String,
130    #[serde(rename = "mimeType")]
131    pub mime_type: String,
132}
133
134//
135// Tool-related Types
136// Types for tool definitions, requests, and responses
137//
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(rename_all = "camelCase")]
141pub struct ToolDefinition {
142    pub name: String,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub description: Option<String>,
145    pub input_schema: serde_json::Value,
146}
147
148#[derive(Debug, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct ListToolsRequest {
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub cursor: Option<String>,
153    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
154    pub meta: Option<serde_json::Value>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
158#[serde(rename_all = "camelCase")]
159pub struct ListToolsResponse {
160    pub tools: Vec<ToolDefinition>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub next_cursor: Option<String>,
163    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
164    pub meta: Option<serde_json::Value>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct CallToolRequest {
170    pub name: String,
171    #[serde(skip_serializing_if = "Option::is_none")]
172    pub arguments: Option<serde_json::Value>,
173    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
174    pub meta: Option<serde_json::Value>,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize)]
178#[serde(rename_all = "camelCase")]
179pub struct CallToolResponse {
180    pub content: Vec<ToolResponseContent>,
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub is_error: Option<bool>,
183    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
184    pub meta: Option<serde_json::Value>,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
188#[serde(tag = "type")]
189pub enum ToolResponseContent {
190    #[serde(rename = "text")]
191    Text { text: String },
192    #[serde(rename = "image")]
193    Image { data: String, mime_type: String },
194    #[serde(rename = "resource")]
195    Resource { resource: ResourceContents },
196}
197
198//
199// Resource-related Types
200// Types for handling resources and their contents
201//
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
204#[serde(rename_all = "camelCase")]
205pub struct ResourceContents {
206    pub uri: Url,
207    #[serde(skip_serializing_if = "Option::is_none")]
208    pub mime_type: Option<String>,
209}
210
211#[derive(Debug, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct ListResourcesRequest {
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub cursor: Option<String>,
216}
217
218#[derive(Debug, Serialize, Deserialize)]
219#[serde(rename_all = "camelCase")]
220pub struct ListResourcesResponse {
221    pub resources: Vec<Resource>,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub next_cursor: Option<String>,
224    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
225    pub meta: Option<HashMap<String, serde_json::Value>>,
226}
227
228#[derive(Debug, Serialize, Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct Resource {
231    pub uri: Url,
232    pub name: String,
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub description: Option<String>,
235    #[serde(skip_serializing_if = "Option::is_none")]
236    pub mime_type: Option<String>,
237}
238
239#[derive(Debug, Serialize, Deserialize)]
240#[serde(rename_all = "camelCase")]
241pub struct ReadResourceRequest {
242    pub uri: Url,
243}
244
245#[derive(Debug, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct ReadResourceResponse {
248    pub contents: Vec<ResourceContent>,
249    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
250    pub meta: Option<HashMap<String, serde_json::Value>>,
251}
252
253#[derive(Debug, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct SubscribeRequest {
256    pub uri: Url,
257}
258
259#[derive(Debug, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase")]
261pub struct UnsubscribeRequest {
262    pub uri: Url,
263}
264
265#[derive(Debug, Serialize, Deserialize)]
266#[serde(rename_all = "camelCase")]
267pub struct ResourceUpdatedNotification {
268    pub uri: Url,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
272#[serde(rename_all = "camelCase")]
273pub struct EmptyResult {
274    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
275    pub meta: Option<HashMap<String, serde_json::Value>>,
276}
277
278#[derive(Debug, Serialize, Deserialize)]
279#[serde(untagged)]
280pub enum ResourceContent {
281    Text(TextResourceContents),
282    Blob(BlobResourceContents),
283}
284
285#[derive(Debug, Serialize, Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct TextResourceContents {
288    pub uri: Url,
289    pub text: String,
290    #[serde(skip_serializing_if = "Option::is_none")]
291    pub mime_type: Option<String>,
292}
293
294#[derive(Debug, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub struct BlobResourceContents {
297    pub uri: Url,
298    pub blob: String, // base64-encoded
299    #[serde(skip_serializing_if = "Option::is_none")]
300    pub mime_type: Option<String>,
301}
302
303//
304// Prompt-related Types
305// Types for handling prompts and their arguments
306//
307
308#[derive(Debug, Serialize, Deserialize)]
309#[serde(rename_all = "camelCase")]
310pub struct ListPromptsRequest {
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub cursor: Option<String>,
313    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
314    pub meta: Option<HashMap<String, serde_json::Value>>,
315}
316
317#[derive(Debug, Deserialize, Serialize)]
318#[serde(rename_all = "camelCase")]
319pub struct ListPromptsResponse {
320    pub prompts: Vec<Prompt>,
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub next_cursor: Option<String>,
323    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
324    pub meta: Option<HashMap<String, serde_json::Value>>,
325}
326
327#[derive(Debug, Deserialize, Serialize)]
328#[serde(rename_all = "camelCase")]
329pub struct Prompt {
330    pub name: String,
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub description: Option<String>,
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub arguments: Option<Vec<PromptArgument>>,
335}
336
337#[derive(Debug, Deserialize, Serialize)]
338#[serde(rename_all = "camelCase")]
339pub struct PromptArgument {
340    pub name: String,
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub description: Option<String>,
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub required: Option<bool>,
345}
346
347#[derive(Debug, Serialize, Deserialize)]
348#[serde(rename_all = "camelCase")]
349pub struct GetPromptRequest {
350    pub name: String,
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub arguments: Option<HashMap<String, String>>,
353}
354
355#[derive(Debug, Serialize, Deserialize)]
356#[serde(rename_all = "camelCase")]
357pub struct GetPromptResponse {
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub description: Option<String>,
360    pub messages: Vec<PromptMessage>,
361    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
362    pub meta: Option<HashMap<String, serde_json::Value>>,
363}
364
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct PromptMessage {
368    pub role: Role,
369    pub content: PromptContent,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
373#[serde(tag = "type")]
374pub enum PromptContent {
375    #[serde(rename = "text")]
376    Text(TextContent),
377    #[serde(rename = "image")]
378    Image(ImageContent),
379    #[serde(rename = "resource")]
380    Resource(EmbeddedResource),
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize)]
384pub struct EmbeddedResource {
385    pub resource: ResourceContents,
386}
387
388//
389// Sampling-related Types
390// Types for model sampling and message creation
391//
392
393#[derive(Debug, Clone, Serialize, Deserialize)]
394#[serde(rename_all = "camelCase")]
395pub struct ModelPreferences {
396    #[serde(skip_serializing_if = "Option::is_none")]
397    pub cost_priority: Option<f32>,
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub intelligence_priority: Option<f32>,
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub speed_priority: Option<f32>,
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub hints: Option<Vec<ModelHint>>,
404}
405
406#[derive(Debug, Clone, Serialize, Deserialize)]
407#[serde(rename_all = "camelCase")]
408pub struct ModelHint {
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub name: Option<String>,
411}
412
413#[derive(Debug, Serialize, Deserialize)]
414#[serde(rename_all = "camelCase")]
415pub struct CreateMessageRequest {
416    pub messages: Vec<SamplingMessage>,
417    pub system_prompt: Option<String>,
418    pub max_tokens: i32,
419    pub temperature: f32,
420    pub include_context: String,
421    pub model_preferences: Option<ModelPreferences>,
422    #[serde(rename = "_meta")]
423    pub meta: Option<HashMap<String, serde_json::Value>>,
424}
425
426#[derive(Debug, Serialize, Deserialize)]
427#[serde(rename_all = "camelCase")]
428pub struct CreateMessageResponse {
429    pub content: MessageContent,
430    pub model: String,
431    pub role: Role,
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub stop_reason: Option<String>,
434    #[serde(rename = "_meta")]
435    pub meta: Option<HashMap<String, serde_json::Value>>,
436}
437
438#[derive(Debug, Serialize, Deserialize)]
439#[serde(rename_all = "camelCase")]
440pub struct SamplingMessage {
441    pub role: Role,
442    pub content: MessageContent,
443}
444
445//
446// Error Handling
447// Error codes and related types
448//
449
450#[derive(Debug, Clone, Copy, PartialEq, Eq)]
451pub enum ErrorCode {
452    // Standard JSON-RPC error codes
453    ParseError = -32700,
454    InvalidRequest = -32600,
455    MethodNotFound = -32601,
456    InvalidParams = -32602,
457    InternalError = -32603,
458}
459
460//
461// Trait Definitions
462// Core traits for protocol handlers
463//
464
465pub trait SamplingHandler: Send + Sync {
466    fn handle_message(
467        &self,
468        request: CreateMessageRequest,
469    ) -> Result<CreateMessageResponse, McpError>;
470}
471
472pub trait NotificationHandler: Send + Sync {
473    fn handle_resource_update(&self, uri: &Url) -> Result<(), McpError>;
474    fn handle_log_message(&self, level: &LogLevel, data: &Value, logger: &Option<String>);
475    fn handle_progress_update(&self, token: &String, progress: &f64, total: &Option<f64>);
476    fn handle_initialized(&self);
477    fn handle_list_changed(&self, method: &NotificationMethod);
478}
479
480//
481// Helper Implementations
482// Additional functionality for core types
483//
484
485impl PromptContent {
486    /// Returns the text content if this content is text
487    pub fn text_content(&self) -> Option<&str> {
488        match self {
489            PromptContent::Text(content) => Some(&content.text),
490            _ => None,
491        }
492    }
493
494    /// Returns the image data and mime type if this content is an image
495    pub fn image_content(&self) -> Option<(&str, &str)> {
496        match self {
497            PromptContent::Image(img) => Some((&img.data, &img.mime_type)),
498            _ => None,
499        }
500    }
501
502    /// Returns the resource URI if this content is a resource
503    pub fn resource_uri(&self) -> Option<&Url> {
504        match self {
505            PromptContent::Resource(res) => Some(&res.resource.uri),
506            _ => None,
507        }
508    }
509
510    /// Returns true if this content is text
511    pub fn is_text(&self) -> bool {
512        matches!(self, PromptContent::Text(_))
513    }
514
515    /// Returns true if this content is an image
516    pub fn is_image(&self) -> bool {
517        matches!(self, PromptContent::Image(_))
518    }
519
520    /// Returns true if this content is a resource
521    pub fn is_resource(&self) -> bool {
522        matches!(self, PromptContent::Resource(_))
523    }
524}
525
526//
527// Tool Schema Generation
528// Currently disabled but to be fixed
529//
530
531/*impl ToolDefinition {
532    pub fn schema_example(&self) -> String {
533        let mut output = String::new();
534        self.build_example(&self.input_schema, &mut output, 0);
535        output
536    }
537
538    fn build_example(&self, schema: &serde_json::Value, output: &mut String, depth: usize) {
539        let indent = "    ".repeat(depth);
540
541        if let Some(props) = schema.get("properties").and_then(|v| v.as_object()) {
542            writeln!(output, "{}", "{").unwrap();
543
544            let mut props_vec: Vec<_> = props.iter().collect();
545            props_vec.sort_by_key(|(name, _)| *name);
546
547            for (i, (name, prop_schema)) in props_vec.iter().enumerate() {
548                write!(output, "{}    \"{}\":", indent, name).unwrap();
549                self.write_type_example(prop_schema, output, depth + 1);
550
551                if i < props_vec.len() - 1 {
552                    write!(output, ",").unwrap();
553                }
554
555                if let Some(desc) = prop_schema.get("description").and_then(|d| d.as_str()) {
556                    write!(output, " // {}", desc).unwrap();
557                }
558
559                writeln!(output).unwrap();
560            }
561
562            write!(output, "{}}}", indent).unwrap();
563        }
564    }
565
566    fn write_type_example(&self, schema: &serde_json::Value, output: &mut String, depth: usize) {
567        let type_name = schema.get("type").and_then(|t| t.as_str()).unwrap_or("any");
568
569        match type_name {
570            "array" => {
571                write!(output, " [").unwrap();
572                if let Some(items) = schema.get("items") {
573                    let items_type = items.get("type").and_then(|t| t.as_str()).unwrap_or("any");
574                    if items_type == "object" {
575                        self.build_example(items, output, depth);
576                    } else {
577                        write!(output, "\"string\"").unwrap();
578                    }
579                }
580                write!(output, "]").unwrap();
581            },
582            "object" => {
583                self.build_example(schema, output, depth);
584            },
585            "string" => write!(output, " \"string\"").unwrap(),
586            "number" => write!(output, " 0").unwrap(),
587            "boolean" => write!(output, " false").unwrap(),
588            _ => write!(output, " null").unwrap(),
589        }
590    }
591}*/