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#[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#[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#[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#[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#[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, #[serde(skip_serializing_if = "Option::is_none")]
300 pub mime_type: Option<String>,
301}
302
303#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
451pub enum ErrorCode {
452 ParseError = -32700,
454 InvalidRequest = -32600,
455 MethodNotFound = -32601,
456 InvalidParams = -32602,
457 InternalError = -32603,
458}
459
460pub 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
480impl PromptContent {
486 pub fn text_content(&self) -> Option<&str> {
488 match self {
489 PromptContent::Text(content) => Some(&content.text),
490 _ => None,
491 }
492 }
493
494 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 pub fn resource_uri(&self) -> Option<&Url> {
504 match self {
505 PromptContent::Resource(res) => Some(&res.resource.uri),
506 _ => None,
507 }
508 }
509
510 pub fn is_text(&self) -> bool {
512 matches!(self, PromptContent::Text(_))
513 }
514
515 pub fn is_image(&self) -> bool {
517 matches!(self, PromptContent::Image(_))
518 }
519
520 pub fn is_resource(&self) -> bool {
522 matches!(self, PromptContent::Resource(_))
523 }
524}
525
526