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