Skip to main content

mcp/
wire.rs

1// SPDX-License-Identifier: Apache-2.0
2//! MCP wire types — the Model Context Protocol message surface. RFC 0004 (client),
3//! RFC 0005 (server).
4//!
5//! Method/notification names are constants (typos become compile errors).
6//! Result/param structs use `camelCase` to match the spec. `content[]` and
7//! resource `contents[]` are kept as `Vec<Value>` with text-extraction helpers
8//! rather than a brittle tagged enum, so an unknown content type from a newer
9//! server is preserved, not a parse error (forward-compat).
10//!
11//! The protocol version + era model lives in [`crate::version`]; it is re-exported
12//! here so `mcp::wire::{PROTOCOL_VERSION, negotiate_version, …}` resolves.
13
14use serde::{Deserialize, Serialize};
15use serde_json::Value;
16
17pub use crate::version::*;
18
19/// Method + notification names (RFC 0004 §wire).
20pub 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    // Tasks extension (io.modelcontextprotocol/tasks): async long-running requests.
37    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    // Modern (2026-07-28+, stateless) methods.
43    /// Query a server's supported versions + capabilities + identity in one call
44    /// (the stateless replacement for the `initialize` capability exchange).
45    pub const SERVER_DISCOVER: &str = "server/discover";
46    /// Open the long-lived notification stream (its SSE response carries the
47    /// change notifications the client opted in to — the stateless replacement
48    /// for the removed GET SSE stream).
49    pub const SUBSCRIPTIONS_LISTEN: &str = "subscriptions/listen";
50
51    // Notifications (no id, no response).
52    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// ---- lifecycle ----
62
63#[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/// Capabilities a client declares. agentd declares **none** in v1 (no roots /
72/// sampling / elicitation / tasks) — RFC 0004 §declare-no-client-caps.
73#[derive(Debug, Clone, Default, Serialize, Deserialize)]
74pub struct ClientCapabilities {
75    #[serde(default, skip_serializing_if = "Option::is_none")]
76    pub experimental: Option<Value>,
77    /// `{}` when this client can deliver a server's `elicitation/create` to a
78    /// human. Omitted otherwise — a server must not ask what we cannot answer.
79    #[serde(default, skip_serializing_if = "Option::is_none")]
80    pub elicitation: Option<Value>,
81    /// `{"listChanged": bool}` when this client answers `roots/list`.
82    #[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/// Result of `server/discover` (modern era): the server's supported protocol
107/// versions, capabilities, and identity in a single call — the stateless
108/// replacement for the legacy `initialize` capability exchange. `resultType` and
109/// the caching fields (`ttlMs`/`cacheScope`) are carried for forward-compat.
110#[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/// What a server says it can do. We gate every call on these (RFC 0004
130/// §capability-gating): no `tools/call` unless `tools` is present; no
131/// `resources/subscribe` unless `resources.subscribe == Some(true)`.
132#[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// ---- tools ----
184
185#[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    /// JSON Schema for the tool's arguments.
194    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/// Result of `tools/call`. `is_error: true` is a **tool-domain** failure (fed
215/// to the model as an observation), distinct from a JSON-RPC transport error
216/// (RFC 0004 §isError).
217#[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    /// Concatenate the `text` parts of `content[]` — what the loop feeds back
233    /// to the model. Non-text parts (image/audio/resource) are summarized by
234    /// type so the model knows they were returned.
235    pub fn text(&self) -> String {
236        content_text(&self.content)
237    }
238}
239
240// ---- resources ----
241
242#[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    /// Each entry is a `{uri, mimeType, text}` or `{uri, mimeType, blob}`
272    /// object; kept as `Value` for forward-compat. Use [`Self::text`].
273    #[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/// A resource **template** (a parameterized `uriTemplate`, RFC 6570) a server
284/// offers via `resources/templates/list` — distinct from a concrete [`Resource`].
285#[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/// `resources/subscribe` / `resources/unsubscribe` params (per-URI only —
308/// templates are NOT subscribable, RFC 0004 §item-vs-list).
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub struct SubscribeParams {
311    pub uri: String,
312}
313
314/// Payload of `notifications/resources/updated` — **URI only** (no diff). The
315/// reactive core re-reads on wake: notify-then-read (RFC 0004 §1.3, RFC 0008).
316#[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
323// ---- tasks extension (io.modelcontextprotocol/tasks) ----
324
325/// The tasks extension identifier — advertised in `capabilities.extensions` to
326/// opt into task-augmented (async long-running) requests.
327pub const TASKS_EXTENSION: &str = "io.modelcontextprotocol/tasks";
328
329/// A durable async-task handle (the tasks extension). A supported request (e.g.
330/// `tools/call`) may return one (`resultType: "task"`) instead of blocking; the
331/// client polls [`method::TASKS_GET`] until a terminal `status`.
332#[derive(Debug, Clone, Default, Serialize, Deserialize)]
333#[serde(rename_all = "camelCase")]
334pub struct Task {
335    pub task_id: String,
336    /// `working` | `input_required` | `completed` | `failed` | `cancelled`.
337    #[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    /// On `completed`: what the original request would have returned.
346    #[serde(default, skip_serializing_if = "Option::is_none")]
347    pub result: Option<Value>,
348    /// On `failed`: the JSON-RPC error.
349    #[serde(default, skip_serializing_if = "Option::is_none")]
350    pub error: Option<Value>,
351    /// On `input_required`: the server's outstanding input requests (MRTR).
352    #[serde(default, skip_serializing_if = "Option::is_none")]
353    pub input_requests: Option<Value>,
354}
355
356impl Task {
357    /// A terminal status (`completed`/`failed`/`cancelled`) — polling stops.
358    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
366/// If a result value is a task handle (`resultType: "task"`), parse it — the
367/// polymorphic shape a task-augmented request returns instead of its normal result.
368pub 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// ---- prompts ----
377
378/// A prompt template a server offers (RFC 0004 §prompts). `arguments` describe
379/// the template's fill-ins.
380#[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/// `prompts/get` params — the template name + its argument fills (all strings).
413#[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/// `prompts/get` result — the rendered messages. `messages[]` is kept as
421/// `Vec<Value>` (each `{role, content}`) for forward-compat with content types.
422#[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// ---- completion ----
431
432/// `completion/complete` params: what to complete (a `ref` to a prompt or
433/// resource template) and the argument being typed. Kept as `Value` — the `ref`
434/// shape varies by target and revision (forward-compat).
435#[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
462/// Extract human-readable text from an MCP `content[]` / `contents[]` array.
463/// Text parts are concatenated; other known parts are noted by type.
464fn 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            // Tool text parts and resource `contents[]` (which omit `type` but
469            // carry `text`) both land here.
470            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        // tools present but subscribe absent -> subscribe denied
505        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        // A tools/call result that is actually a task handle.
556        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        // A normal (non-task) result is not a task.
563        assert!(as_task_result(&json!({"content": []})).is_none());
564        // Terminal / input states.
565        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}