1use serde::{Deserialize, Serialize};
15use serde_json::Value;
16
17pub use crate::version::*;
18
19pub mod method {
22 pub const INITIALIZE: &str = "initialize";
23 pub const INITIALIZED: &str = "notifications/initialized";
24 pub const PING: &str = "ping";
25 pub const TOOLS_LIST: &str = "tools/list";
26 pub const TOOLS_CALL: &str = "tools/call";
27 pub const RESOURCES_LIST: &str = "resources/list";
28 pub const RESOURCES_READ: &str = "resources/read";
29 pub const RESOURCES_SUBSCRIBE: &str = "resources/subscribe";
30 pub const RESOURCES_UNSUBSCRIBE: &str = "resources/unsubscribe";
31 pub const RESOURCES_TEMPLATES_LIST: &str = "resources/templates/list";
32 pub const PROMPTS_LIST: &str = "prompts/list";
33 pub const PROMPTS_GET: &str = "prompts/get";
34 pub const COMPLETION_COMPLETE: &str = "completion/complete";
35 pub const LOGGING_SET_LEVEL: &str = "logging/setLevel";
36
37 pub const TASKS_GET: &str = "tasks/get";
39 pub const TASKS_UPDATE: &str = "tasks/update";
40 pub const TASKS_CANCEL: &str = "tasks/cancel";
41 pub const NOTIFY_TASKS: &str = "notifications/tasks";
42
43 pub const SERVER_DISCOVER: &str = "server/discover";
47 pub const SUBSCRIPTIONS_LISTEN: &str = "subscriptions/listen";
51
52 pub const NOTIFY_RESOURCES_UPDATED: &str = "notifications/resources/updated";
54 pub const NOTIFY_RESOURCES_LIST_CHANGED: &str = "notifications/resources/list_changed";
55 pub const NOTIFY_TOOLS_LIST_CHANGED: &str = "notifications/tools/list_changed";
56 pub const NOTIFY_SUBSCRIPTIONS_ACK: &str = "notifications/subscriptions/acknowledged";
57 pub const NOTIFY_CANCELLED: &str = "notifications/cancelled";
58 pub const NOTIFY_PROGRESS: &str = "notifications/progress";
59 pub const NOTIFY_MESSAGE: &str = "notifications/message";
60}
61
62#[derive(Debug, Clone, Default, Serialize, Deserialize)]
65pub struct Implementation {
66 pub name: String,
67 pub version: String,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub title: Option<String>,
70}
71
72#[derive(Debug, Clone, Default, Serialize, Deserialize)]
76pub struct ClientCapabilities {
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub experimental: Option<Value>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub elicitation: Option<Value>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub roots: Option<Value>,
86}
87
88#[derive(Debug, Clone, Default, Serialize, Deserialize)]
89#[serde(rename_all = "camelCase")]
90pub struct InitializeParams {
91 pub protocol_version: String,
92 pub capabilities: ClientCapabilities,
93 pub client_info: Implementation,
94}
95
96#[derive(Debug, Clone, Default, Serialize, Deserialize)]
97#[serde(rename_all = "camelCase")]
98pub struct InitializeResult {
99 pub protocol_version: String,
100 #[serde(default)]
101 pub capabilities: ServerCapabilities,
102 #[serde(default)]
103 pub server_info: Option<Implementation>,
104 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub instructions: Option<String>,
106}
107
108#[derive(Debug, Clone, Default, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct DiscoverResult {
115 #[serde(default, skip_serializing_if = "Option::is_none")]
116 pub result_type: Option<String>,
117 #[serde(default)]
118 pub supported_versions: Vec<String>,
119 #[serde(default)]
120 pub capabilities: ServerCapabilities,
121 #[serde(default)]
122 pub server_info: Option<Implementation>,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub instructions: Option<String>,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub ttl_ms: Option<u64>,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
128 pub cache_scope: Option<String>,
129}
130
131#[derive(Debug, Clone, Default, Serialize, Deserialize)]
136pub struct ServerCapabilities {
137 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub tools: Option<ToolsCapability>,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub resources: Option<ResourcesCapability>,
141 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub prompts: Option<Value>,
143 #[serde(default, skip_serializing_if = "Option::is_none")]
144 pub logging: Option<Value>,
145 #[serde(default, skip_serializing_if = "Option::is_none")]
146 pub completions: Option<Value>,
147}
148
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
150#[serde(rename_all = "camelCase")]
151pub struct ToolsCapability {
152 #[serde(default, skip_serializing_if = "Option::is_none")]
153 pub list_changed: Option<bool>,
154}
155
156#[derive(Debug, Clone, Default, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub struct ResourcesCapability {
159 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub subscribe: Option<bool>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub list_changed: Option<bool>,
163}
164
165impl ServerCapabilities {
166 pub fn supports_tools(&self) -> bool {
167 self.tools.is_some()
168 }
169 pub fn supports_resources(&self) -> bool {
170 self.resources.is_some()
171 }
172 pub fn supports_subscribe(&self) -> bool {
173 self.resources
174 .as_ref()
175 .and_then(|r| r.subscribe)
176 .unwrap_or(false)
177 }
178 pub fn supports_prompts(&self) -> bool {
179 self.prompts.is_some()
180 }
181 pub fn supports_completions(&self) -> bool {
182 self.completions.is_some()
183 }
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct Tool {
191 pub name: String,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
193 pub title: Option<String>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
195 pub description: Option<String>,
196 pub input_schema: Value,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
199 pub output_schema: Option<Value>,
200}
201
202#[derive(Debug, Clone, Default, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct ListToolsResult {
205 pub tools: Vec<Tool>,
206 #[serde(default, skip_serializing_if = "Option::is_none")]
207 pub next_cursor: Option<String>,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct CallToolParams {
212 pub name: String,
213 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub arguments: Option<Value>,
215}
216
217#[derive(Debug, Clone, Default, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct CallToolResult {
223 #[serde(default)]
224 pub content: Vec<Value>,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub is_error: Option<bool>,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub structured_content: Option<Value>,
229}
230
231impl CallToolResult {
232 pub fn is_error(&self) -> bool {
233 self.is_error.unwrap_or(false)
234 }
235 pub fn text(&self) -> String {
239 content_text(&self.content)
240 }
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct Resource {
248 pub uri: String,
249 #[serde(default, skip_serializing_if = "Option::is_none")]
250 pub name: Option<String>,
251 #[serde(default, skip_serializing_if = "Option::is_none")]
252 pub title: Option<String>,
253 #[serde(default, skip_serializing_if = "Option::is_none")]
254 pub description: Option<String>,
255 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub mime_type: Option<String>,
257}
258
259#[derive(Debug, Clone, Default, Serialize, Deserialize)]
260#[serde(rename_all = "camelCase")]
261pub struct ListResourcesResult {
262 pub resources: Vec<Resource>,
263 #[serde(default, skip_serializing_if = "Option::is_none")]
264 pub next_cursor: Option<String>,
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct ReadResourceParams {
269 pub uri: String,
270}
271
272#[derive(Debug, Clone, Default, Serialize, Deserialize)]
273pub struct ReadResourceResult {
274 #[serde(default)]
277 pub contents: Vec<Value>,
278}
279
280impl ReadResourceResult {
281 pub fn text(&self) -> String {
282 content_text(&self.contents)
283 }
284}
285
286#[derive(Debug, Clone, Serialize, Deserialize)]
289#[serde(rename_all = "camelCase")]
290pub struct ResourceTemplate {
291 pub uri_template: String,
292 #[serde(default, skip_serializing_if = "Option::is_none")]
293 pub name: Option<String>,
294 #[serde(default, skip_serializing_if = "Option::is_none")]
295 pub title: Option<String>,
296 #[serde(default, skip_serializing_if = "Option::is_none")]
297 pub description: Option<String>,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub mime_type: Option<String>,
300}
301
302#[derive(Debug, Clone, Default, Serialize, Deserialize)]
303#[serde(rename_all = "camelCase")]
304pub struct ListResourceTemplatesResult {
305 pub resource_templates: Vec<ResourceTemplate>,
306 #[serde(default, skip_serializing_if = "Option::is_none")]
307 pub next_cursor: Option<String>,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct SubscribeParams {
315 pub uri: String,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct ResourceUpdatedParams {
324 pub uri: String,
325 #[serde(default, skip_serializing_if = "Option::is_none")]
326 pub title: Option<String>,
327}
328
329pub const TASKS_EXTENSION: &str = "io.modelcontextprotocol/tasks";
334
335#[derive(Debug, Clone, Default, Serialize, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct Task {
341 pub task_id: String,
342 #[serde(default)]
344 pub status: String,
345 #[serde(default, skip_serializing_if = "Option::is_none")]
346 pub status_message: Option<String>,
347 #[serde(default, skip_serializing_if = "Option::is_none")]
348 pub ttl_ms: Option<u64>,
349 #[serde(default, skip_serializing_if = "Option::is_none")]
350 pub poll_interval_ms: Option<u64>,
351 #[serde(default, skip_serializing_if = "Option::is_none")]
353 pub result: Option<Value>,
354 #[serde(default, skip_serializing_if = "Option::is_none")]
356 pub error: Option<Value>,
357 #[serde(default, skip_serializing_if = "Option::is_none")]
359 pub input_requests: Option<Value>,
360}
361
362impl Task {
363 pub fn is_terminal(&self) -> bool {
365 matches!(self.status.as_str(), "completed" | "failed" | "cancelled")
366 }
367 pub fn needs_input(&self) -> bool {
368 self.status == "input_required"
369 }
370}
371
372pub fn as_task_result(result: &Value) -> Option<Task> {
375 if result.get("resultType").and_then(Value::as_str) == Some("task") {
376 serde_json::from_value(result.clone()).ok()
377 } else {
378 None
379 }
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize)]
387#[serde(rename_all = "camelCase")]
388pub struct Prompt {
389 pub name: String,
390 #[serde(default, skip_serializing_if = "Option::is_none")]
391 pub title: Option<String>,
392 #[serde(default, skip_serializing_if = "Option::is_none")]
393 pub description: Option<String>,
394 #[serde(default, skip_serializing_if = "Vec::is_empty")]
395 pub arguments: Vec<PromptArgument>,
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
399#[serde(rename_all = "camelCase")]
400pub struct PromptArgument {
401 pub name: String,
402 #[serde(default, skip_serializing_if = "Option::is_none")]
403 pub title: Option<String>,
404 #[serde(default, skip_serializing_if = "Option::is_none")]
405 pub description: Option<String>,
406 #[serde(default, skip_serializing_if = "Option::is_none")]
407 pub required: Option<bool>,
408}
409
410#[derive(Debug, Clone, Default, Serialize, Deserialize)]
411#[serde(rename_all = "camelCase")]
412pub struct ListPromptsResult {
413 pub prompts: Vec<Prompt>,
414 #[serde(default, skip_serializing_if = "Option::is_none")]
415 pub next_cursor: Option<String>,
416}
417
418#[derive(Debug, Clone, Serialize, Deserialize)]
420pub struct GetPromptParams {
421 pub name: String,
422 #[serde(default, skip_serializing_if = "Option::is_none")]
423 pub arguments: Option<Value>,
424}
425
426#[derive(Debug, Clone, Default, Serialize, Deserialize)]
429pub struct GetPromptResult {
430 #[serde(default, skip_serializing_if = "Option::is_none")]
431 pub description: Option<String>,
432 #[serde(default)]
433 pub messages: Vec<Value>,
434}
435
436#[derive(Debug, Clone, Serialize, Deserialize)]
442#[serde(rename_all = "camelCase")]
443pub struct CompleteParams {
444 #[serde(rename = "ref")]
445 pub reference: Value,
446 pub argument: Value,
447 #[serde(default, skip_serializing_if = "Option::is_none")]
448 pub context: Option<Value>,
449}
450
451#[derive(Debug, Clone, Default, Serialize, Deserialize)]
452pub struct CompleteResult {
453 #[serde(default)]
454 pub completion: Completion,
455}
456
457#[derive(Debug, Clone, Default, Serialize, Deserialize)]
458#[serde(rename_all = "camelCase")]
459pub struct Completion {
460 #[serde(default)]
461 pub values: Vec<String>,
462 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub total: Option<u64>,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
465 pub has_more: Option<bool>,
466}
467
468fn content_text(items: &[Value]) -> String {
471 let mut parts: Vec<String> = Vec::new();
472 for item in items {
473 match item.get("type").and_then(Value::as_str) {
474 Some("text") | None => {
477 if let Some(t) = item.get("text").and_then(Value::as_str) {
478 parts.push(t.to_string());
479 }
480 }
481 Some(other) => parts.push(format!("[{other} content]")),
482 }
483 }
484 parts.join("\n")
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use serde_json::json;
491
492 #[test]
493 fn initialize_result_parses_capabilities() {
494 let json = r#"{
495 "protocolVersion": "2025-11-25",
496 "capabilities": {"tools": {"listChanged": true}, "resources": {"subscribe": true}},
497 "serverInfo": {"name": "fs", "version": "1.0"}
498 }"#;
499 let r: InitializeResult = serde_json::from_str(json).unwrap();
500 assert_eq!(r.protocol_version, "2025-11-25");
501 assert!(r.capabilities.supports_tools());
502 assert!(r.capabilities.supports_subscribe());
503 }
504
505 #[test]
506 fn capability_gating_defaults_closed() {
507 let caps = ServerCapabilities::default();
508 assert!(!caps.supports_tools());
509 assert!(!caps.supports_subscribe());
510 let json = r#"{"tools": {}, "resources": {"listChanged": true}}"#;
512 let caps: ServerCapabilities = serde_json::from_str(json).unwrap();
513 assert!(caps.supports_tools());
514 assert!(!caps.supports_subscribe());
515 }
516
517 #[test]
518 fn call_tool_result_text_and_error() {
519 let json = r#"{"content": [{"type": "text", "text": "hello"}, {"type": "image", "data": "..."}], "isError": false}"#;
520 let r: CallToolResult = serde_json::from_str(json).unwrap();
521 assert!(!r.is_error());
522 assert!(r.text().contains("hello"));
523 assert!(r.text().contains("[image content]"));
524 }
525
526 #[test]
527 fn updated_notification_is_uri_only() {
528 let json = r#"{"uri": "file:///data/in.json"}"#;
529 let p: ResourceUpdatedParams = serde_json::from_str(json).unwrap();
530 assert_eq!(p.uri, "file:///data/in.json");
531 assert!(p.title.is_none());
532 }
533
534 #[test]
535 fn tool_list_pagination_cursor() {
536 let json = r#"{"tools": [{"name": "read_file", "inputSchema": {"type": "object"}}], "nextCursor": "abc"}"#;
537 let r: ListToolsResult = serde_json::from_str(json).unwrap();
538 assert_eq!(r.tools.len(), 1);
539 assert_eq!(r.next_cursor.as_deref(), Some("abc"));
540 }
541
542 #[test]
543 fn discover_result_parses() {
544 let json = r#"{
545 "resultType": "complete",
546 "supportedVersions": ["2026-07-28", "2025-11-25"],
547 "capabilities": {"tools": {}, "resources": {"subscribe": true}, "prompts": {}},
548 "serverInfo": {"name": "s", "version": "1"},
549 "ttlMs": 3600000, "cacheScope": "public"
550 }"#;
551 let d: DiscoverResult = serde_json::from_str(json).unwrap();
552 assert_eq!(d.supported_versions, ["2026-07-28", "2025-11-25"]);
553 assert!(d.capabilities.supports_tools());
554 assert!(d.capabilities.supports_subscribe());
555 assert!(d.capabilities.supports_prompts());
556 assert_eq!(d.ttl_ms, Some(3_600_000));
557 }
558
559 #[test]
560 fn task_result_detected_and_lifecycle() {
561 let create = json!({"resultType": "task", "taskId": "t-1", "status": "working",
563 "pollIntervalMs": 250, "ttlMs": 60000});
564 let t = as_task_result(&create).expect("is a task result");
565 assert_eq!(t.task_id, "t-1");
566 assert_eq!(t.poll_interval_ms, Some(250));
567 assert!(!t.is_terminal());
568 assert!(as_task_result(&json!({"content": []})).is_none());
570 let done: Task = serde_json::from_value(
572 json!({"taskId": "t-1", "status": "completed", "result": {"content": []}}),
573 )
574 .unwrap();
575 assert!(done.is_terminal() && !done.needs_input());
576 let ask: Task = serde_json::from_value(
577 json!({"taskId": "t-1", "status": "input_required", "inputRequests": {}}),
578 )
579 .unwrap();
580 assert!(ask.needs_input() && !ask.is_terminal());
581 }
582
583 #[test]
584 fn prompts_and_completion_parse() {
585 let list: ListPromptsResult = serde_json::from_str(
586 r#"{"prompts": [{"name": "greet", "arguments": [{"name": "who", "required": true}]}]}"#,
587 )
588 .unwrap();
589 assert_eq!(list.prompts[0].name, "greet");
590 assert_eq!(list.prompts[0].arguments[0].name, "who");
591 assert_eq!(list.prompts[0].arguments[0].required, Some(true));
592
593 let got: GetPromptResult = serde_json::from_str(
594 r#"{"description": "d", "messages": [{"role": "user", "content": {"type": "text", "text": "hi"}}]}"#,
595 )
596 .unwrap();
597 assert_eq!(got.messages.len(), 1);
598
599 let comp: CompleteResult = serde_json::from_str(
600 r#"{"completion": {"values": ["alice", "bob"], "hasMore": false}}"#,
601 )
602 .unwrap();
603 assert_eq!(comp.completion.values, ["alice", "bob"]);
604 assert_eq!(comp.completion.has_more, Some(false));
605 }
606}