Skip to main content

mcplease/
types.rs

1//! Serialization types for the Model Context Protocol.
2//!
3//! Shapes follow the `2026-07-28` schema revision (reference copies under
4//! `spec/`), plus the stateful `initialize` handshake from `2025-11-25` and
5//! earlier: `2026-07-28` removed the handshake in favor of stateless
6//! per-request `_meta`, but every earlier revision — and most deployed
7//! servers — still begins with `initialize`. Fields serialize in the spec's
8//! camelCase; optional fields are omitted rather than serialized as null.
9//!
10//! Fields that `2026-07-28` requires but earlier revisions lack (for
11//! example `resultType`, `ttlMs`, `cacheScope`) are `Option` here so the
12//! same types parse messages from servers on any revision; the spec directs
13//! clients to treat an absent `resultType` as `"complete"`.
14
15use serde::{Deserialize, Deserializer, Serialize};
16use serde_json::{Map, Value};
17use std::borrow::Cow;
18
19/// The newest protocol revision these types model.
20pub const LATEST_PROTOCOL_VERSION: &str = "2026-07-28";
21
22/// The newest revision that begins with the `initialize` handshake.
23/// Revisions after this are stateless and carry version/capability
24/// information in per-request `_meta` instead.
25pub const LATEST_HANDSHAKE_PROTOCOL_VERSION: &str = "2025-11-25";
26
27/// Every revision the types in this module can represent, newest first.
28pub const SUPPORTED_PROTOCOL_VERSIONS: &[&str] = &[
29    "2026-07-28",
30    "2025-11-25",
31    "2025-06-18",
32    "2025-03-26",
33    "2024-11-05",
34];
35
36// --- JSON-RPC envelope ---
37
38/// A JSON-RPC request id: a string or an integer.
39#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
40#[serde(untagged)]
41pub enum RequestId {
42    String(String),
43    Integer(i64),
44}
45
46impl From<i64> for RequestId {
47    fn from(value: i64) -> Self {
48        Self::Integer(value)
49    }
50}
51
52impl From<String> for RequestId {
53    fn from(value: String) -> Self {
54        Self::String(value)
55    }
56}
57
58impl From<&str> for RequestId {
59    fn from(value: &str) -> Self {
60        Self::String(value.into())
61    }
62}
63
64/// A request that expects a response.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct JsonRpcRequest {
67    pub jsonrpc: Cow<'static, str>,
68    pub id: RequestId,
69    pub method: String,
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub params: Option<Value>,
72}
73
74impl JsonRpcRequest {
75    pub fn new(id: impl Into<RequestId>, method: impl Into<String>, params: Option<Value>) -> Self {
76        Self {
77            jsonrpc: Cow::Borrowed("2.0"),
78            id: id.into(),
79            method: method.into(),
80            params,
81        }
82    }
83}
84
85/// A one-way message that expects no response.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct JsonRpcNotification {
88    pub jsonrpc: Cow<'static, str>,
89    pub method: String,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub params: Option<Value>,
92}
93
94impl JsonRpcNotification {
95    pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
96        Self {
97            jsonrpc: Cow::Borrowed("2.0"),
98            method: method.into(),
99            params,
100        }
101    }
102}
103
104/// A response to a request: exactly one of `result` or `error` is present.
105/// Error responses may omit `id` when the request id could not be read.
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct JsonRpcResponse {
108    pub jsonrpc: Cow<'static, str>,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub id: Option<RequestId>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub result: Option<Value>,
113    #[serde(skip_serializing_if = "Option::is_none")]
114    pub error: Option<JsonRpcError>,
115}
116
117impl JsonRpcResponse {
118    pub fn success(id: RequestId, result: impl Serialize) -> Self {
119        Self {
120            jsonrpc: Cow::Borrowed("2.0"),
121            id: Some(id),
122            result: Some(serde_json::to_value(result).unwrap_or(Value::Null)),
123            error: None,
124        }
125    }
126
127    pub fn error(id: impl Into<Option<RequestId>>, error: JsonRpcError) -> Self {
128        Self {
129            jsonrpc: Cow::Borrowed("2.0"),
130            id: id.into(),
131            result: None,
132            error: Some(error),
133        }
134    }
135
136    /// The response as `Ok(result)` or `Err(error)`. A response carrying
137    /// neither (invalid JSON-RPC) comes back as an internal error.
138    pub fn into_result(self) -> Result<Value, JsonRpcError> {
139        match (self.result, self.error) {
140            (_, Some(error)) => Err(error),
141            (Some(result), None) => Ok(result),
142            (None, None) => Err(JsonRpcError {
143                code: error_codes::INTERNAL_ERROR,
144                message: "response carried neither result nor error".into(),
145                data: None,
146            }),
147        }
148    }
149}
150
151/// JSON-RPC error codes, including the MCP-reserved allocations
152/// (`2026-07-28` reserves `-32020..=-32099` for the specification).
153pub mod error_codes {
154    pub const PARSE_ERROR: i64 = -32700;
155    pub const INVALID_REQUEST: i64 = -32600;
156    pub const METHOD_NOT_FOUND: i64 = -32601;
157    pub const INVALID_PARAMS: i64 = -32602;
158    pub const INTERNAL_ERROR: i64 = -32603;
159    pub const HEADER_MISMATCH: i64 = -32020;
160    pub const MISSING_REQUIRED_CLIENT_CAPABILITY: i64 = -32021;
161    pub const UNSUPPORTED_PROTOCOL_VERSION: i64 = -32022;
162}
163
164/// The error member of an error response.
165#[derive(Debug, Clone, Serialize, Deserialize)]
166pub struct JsonRpcError {
167    pub code: i64,
168    pub message: String,
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub data: Option<Value>,
171}
172
173impl JsonRpcError {
174    pub fn method_not_found(method: &str) -> Self {
175        Self {
176            code: error_codes::METHOD_NOT_FOUND,
177            message: format!("unknown method: {method}"),
178            data: None,
179        }
180    }
181
182    pub fn invalid_params(message: impl Into<String>) -> Self {
183        Self {
184            code: error_codes::INVALID_PARAMS,
185            message: message.into(),
186            data: None,
187        }
188    }
189
190    pub fn internal(message: impl Into<String>) -> Self {
191        Self {
192            code: error_codes::INTERNAL_ERROR,
193            message: message.into(),
194            data: None,
195        }
196    }
197}
198
199impl std::fmt::Display for JsonRpcError {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        write!(f, "{} (code {})", self.message, self.code)
202    }
203}
204
205impl std::error::Error for JsonRpcError {}
206
207/// Any message that can arrive on a transport, discriminated by shape:
208/// `method` + `id` is a request, `method` alone is a notification, and
209/// `result`/`error` is a response.
210#[derive(Debug, Clone, Serialize)]
211#[serde(untagged)]
212pub enum JsonRpcMessage {
213    Request(JsonRpcRequest),
214    Notification(JsonRpcNotification),
215    Response(JsonRpcResponse),
216}
217
218impl<'de> Deserialize<'de> for JsonRpcMessage {
219    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
220    where
221        D: Deserializer<'de>,
222    {
223        use serde::de::Error;
224        let value = Value::deserialize(deserializer)?;
225        let object = value
226            .as_object()
227            .ok_or_else(|| D::Error::custom("expected a JSON-RPC object"))?;
228        if object.contains_key("method") {
229            if object.contains_key("id") {
230                serde_json::from_value(value).map(Self::Request)
231            } else {
232                serde_json::from_value(value).map(Self::Notification)
233            }
234            .map_err(D::Error::custom)
235        } else if object.contains_key("result") || object.contains_key("error") {
236            serde_json::from_value(value)
237                .map(Self::Response)
238                .map_err(D::Error::custom)
239        } else {
240            Err(D::Error::custom(
241                "object is neither a request, a notification, nor a response",
242            ))
243        }
244    }
245}
246
247// --- identity and capabilities ---
248
249/// Describes an MCP implementation (a client or a server).
250#[derive(Debug, Clone, Serialize, Deserialize)]
251#[serde(rename_all = "camelCase")]
252pub struct Implementation {
253    pub name: Cow<'static, str>,
254    pub version: Cow<'static, str>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub title: Option<String>,
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub description: Option<String>,
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub website_url: Option<String>,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub icons: Option<Vec<Icon>>,
263}
264
265impl Implementation {
266    pub fn new(name: impl Into<Cow<'static, str>>, version: impl Into<Cow<'static, str>>) -> Self {
267        Self {
268            name: name.into(),
269            version: version.into(),
270            title: None,
271            description: None,
272            website_url: None,
273            icons: None,
274        }
275    }
276}
277
278/// An optionally-sized icon for display in a user interface.
279#[derive(Debug, Clone, Serialize, Deserialize)]
280#[serde(rename_all = "camelCase")]
281pub struct Icon {
282    pub src: String,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub mime_type: Option<String>,
285    #[serde(skip_serializing_if = "Option::is_none")]
286    pub sizes: Option<Vec<String>>,
287    #[serde(skip_serializing_if = "Option::is_none")]
288    pub theme: Option<String>,
289}
290
291/// Capabilities a client advertises. The leaf shapes this crate does not
292/// interpret stay as raw JSON.
293#[derive(Debug, Clone, Default, Serialize, Deserialize)]
294#[serde(rename_all = "camelCase")]
295pub struct ClientCapabilities {
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub elicitation: Option<Value>,
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub roots: Option<Value>,
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub sampling: Option<Value>,
302    #[serde(skip_serializing_if = "Option::is_none")]
303    pub experimental: Option<Map<String, Value>>,
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub extensions: Option<Map<String, Value>>,
306}
307
308/// Capabilities a server advertises.
309#[derive(Debug, Clone, Default, Serialize, Deserialize)]
310#[serde(rename_all = "camelCase")]
311pub struct ServerCapabilities {
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub tools: Option<ToolsCapability>,
314    #[serde(skip_serializing_if = "Option::is_none")]
315    pub prompts: Option<PromptsCapability>,
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub resources: Option<ResourcesCapability>,
318    #[serde(skip_serializing_if = "Option::is_none")]
319    pub completions: Option<Value>,
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub logging: Option<Value>,
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub experimental: Option<Map<String, Value>>,
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub extensions: Option<Map<String, Value>>,
326}
327
328impl ServerCapabilities {
329    /// Capabilities advertising tools only — what this crate's serve loop offers.
330    pub fn tools_only() -> Self {
331        Self {
332            tools: Some(ToolsCapability::default()),
333            ..Self::default()
334        }
335    }
336}
337
338#[derive(Debug, Clone, Default, Serialize, Deserialize)]
339#[serde(rename_all = "camelCase")]
340pub struct ToolsCapability {
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub list_changed: Option<bool>,
343}
344
345#[derive(Debug, Clone, Default, Serialize, Deserialize)]
346#[serde(rename_all = "camelCase")]
347pub struct PromptsCapability {
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub list_changed: Option<bool>,
350}
351
352#[derive(Debug, Clone, Default, Serialize, Deserialize)]
353#[serde(rename_all = "camelCase")]
354pub struct ResourcesCapability {
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub list_changed: Option<bool>,
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub subscribe: Option<bool>,
359}
360
361// --- lifecycle ---
362
363/// `initialize` request params (protocol revisions through `2025-11-25`;
364/// removed in `2026-07-28`).
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(rename_all = "camelCase")]
367pub struct InitializeRequestParams {
368    pub protocol_version: String,
369    #[serde(default)]
370    pub capabilities: ClientCapabilities,
371    pub client_info: Implementation,
372}
373
374/// `initialize` result. The server echoes the requested protocol version
375/// when it supports it, and otherwise answers with the newest version it
376/// does support; the client then decides whether to continue.
377#[derive(Debug, Clone, Serialize, Deserialize)]
378#[serde(rename_all = "camelCase")]
379pub struct InitializeResult {
380    pub protocol_version: String,
381    #[serde(default)]
382    pub capabilities: ServerCapabilities,
383    pub server_info: Implementation,
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub instructions: Option<String>,
386    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
387    pub meta: Option<Map<String, Value>>,
388}
389
390/// `server/discover` result (`2026-07-28`): the stateless replacement for
391/// the handshake, and the backward-compatibility probe — a pre-`2026-07-28`
392/// server answers it with method-not-found, telling the client to fall back
393/// to `initialize`.
394#[derive(Debug, Clone, Serialize, Deserialize)]
395#[serde(rename_all = "camelCase")]
396pub struct DiscoverResult {
397    pub supported_versions: Vec<String>,
398    #[serde(default)]
399    pub capabilities: ServerCapabilities,
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub instructions: Option<String>,
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub ttl_ms: Option<u64>,
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub cache_scope: Option<CacheScope>,
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub result_type: Option<ResultType>,
408    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
409    pub meta: Option<Map<String, Value>>,
410}
411
412/// Cache scope for a cacheable result (`2026-07-28`), analogous to HTTP
413/// `Cache-Control: public` vs `private`.
414#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
415#[serde(rename_all = "lowercase")]
416pub enum CacheScope {
417    Public,
418    Private,
419}
420
421/// Discriminates a result as final or interim.
422///
423/// `2026-07-28` requires this field on every result. The spec directs clients
424/// to treat an absent value — from a server on an earlier revision — as
425/// [`Complete`](ResultType::Complete). Unrecognized values from a future
426/// revision are preserved in [`Other`](ResultType::Other) rather than failing
427/// to parse.
428#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
429#[serde(rename_all = "snake_case")]
430pub enum ResultType {
431    /// An ordinary, final result.
432    #[default]
433    Complete,
434    /// An interim result: the server needs more input before it can finish.
435    /// See [`InputRequiredResult`].
436    InputRequired,
437    #[serde(untagged)]
438    Other(String),
439}
440
441/// Well-known `_meta` keys reserved by the specification.
442///
443/// Any prefix whose second label is `modelcontextprotocol` or `mcp` is
444/// reserved for MCP use, so these must not be invented locally.
445pub mod meta_keys {
446    /// Required on every request (`2026-07-28`).
447    pub const PROTOCOL_VERSION: &str = "io.modelcontextprotocol/protocolVersion";
448    /// Clients SHOULD send this on every request.
449    pub const CLIENT_INFO: &str = "io.modelcontextprotocol/clientInfo";
450    /// Required on every request. Declared per-request; servers MUST NOT infer
451    /// it from prior requests.
452    pub const CLIENT_CAPABILITIES: &str = "io.modelcontextprotocol/clientCapabilities";
453    /// Servers SHOULD include this in every result.
454    pub const SERVER_INFO: &str = "io.modelcontextprotocol/serverInfo";
455    /// Per-request log level. Deprecated in `2026-07-28` along with Logging.
456    pub const LOG_LEVEL: &str = "io.modelcontextprotocol/logLevel";
457    /// Correlates a notification with the `subscriptions/listen` stream it
458    /// arrived on.
459    pub const SUBSCRIPTION_ID: &str = "io.modelcontextprotocol/subscriptionId";
460}
461
462// --- tools ---
463
464/// Definition of a tool the client can call.
465#[derive(Debug, Clone, Serialize, Deserialize)]
466#[serde(rename_all = "camelCase")]
467pub struct Tool {
468    pub name: String,
469    /// A JSON Schema object (`type: "object"` at the root; any JSON Schema
470    /// 2020-12 keywords beyond that). Kept as raw JSON rather than a typed
471    /// subset so nothing a server advertises is lost in a round-trip.
472    pub input_schema: Value,
473    #[serde(skip_serializing_if = "Option::is_none")]
474    pub title: Option<String>,
475    #[serde(skip_serializing_if = "Option::is_none")]
476    pub description: Option<String>,
477    #[serde(skip_serializing_if = "Option::is_none")]
478    pub output_schema: Option<Value>,
479    #[serde(skip_serializing_if = "Option::is_none")]
480    pub annotations: Option<ToolAnnotations>,
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub icons: Option<Vec<Icon>>,
483    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
484    pub meta: Option<Map<String, Value>>,
485}
486
487impl Tool {
488    pub fn new(name: impl Into<String>, input_schema: Value) -> Self {
489        Self {
490            name: name.into(),
491            input_schema,
492            title: None,
493            description: None,
494            output_schema: None,
495            annotations: None,
496            icons: None,
497            meta: None,
498        }
499    }
500}
501
502/// Behavior hints for a tool. All properties are hints — the spec warns
503/// clients not to make tool-use decisions on them for untrusted servers.
504#[derive(Debug, Clone, Default, Serialize, Deserialize)]
505#[serde(rename_all = "camelCase")]
506pub struct ToolAnnotations {
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub title: Option<String>,
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub read_only_hint: Option<bool>,
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub destructive_hint: Option<bool>,
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub idempotent_hint: Option<bool>,
515    #[serde(skip_serializing_if = "Option::is_none")]
516    pub open_world_hint: Option<bool>,
517}
518
519/// `tools/list` result.
520#[derive(Debug, Clone, Default, Serialize, Deserialize)]
521#[serde(rename_all = "camelCase")]
522pub struct ListToolsResult {
523    pub tools: Vec<Tool>,
524    #[serde(skip_serializing_if = "Option::is_none")]
525    pub next_cursor: Option<String>,
526    #[serde(skip_serializing_if = "Option::is_none")]
527    pub ttl_ms: Option<u64>,
528    #[serde(skip_serializing_if = "Option::is_none")]
529    pub cache_scope: Option<CacheScope>,
530    #[serde(skip_serializing_if = "Option::is_none")]
531    pub result_type: Option<ResultType>,
532    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
533    pub meta: Option<Map<String, Value>>,
534}
535
536/// `tools/call` request params.
537#[derive(Debug, Clone, Serialize, Deserialize)]
538#[serde(rename_all = "camelCase")]
539pub struct CallToolRequestParams {
540    pub name: String,
541    #[serde(skip_serializing_if = "Option::is_none")]
542    pub arguments: Option<Map<String, Value>>,
543    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
544    pub meta: Option<Map<String, Value>>,
545}
546
547/// `tools/call` result. A failure *of the tool* is reported here with
548/// `is_error: true` so the model can see it and self-correct; a JSON-RPC
549/// error response is reserved for failures of the protocol (unknown tool,
550/// malformed params).
551#[derive(Debug, Clone, Default, Serialize, Deserialize)]
552#[serde(rename_all = "camelCase")]
553pub struct CallToolResult {
554    pub content: Vec<ContentBlock>,
555    #[serde(skip_serializing_if = "Option::is_none")]
556    pub structured_content: Option<Value>,
557    #[serde(skip_serializing_if = "Option::is_none")]
558    pub is_error: Option<bool>,
559    #[serde(skip_serializing_if = "Option::is_none")]
560    pub result_type: Option<ResultType>,
561    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
562    pub meta: Option<Map<String, Value>>,
563}
564
565impl CallToolResult {
566    pub fn text(text: impl Into<String>) -> Self {
567        Self {
568            content: vec![ContentBlock::text(text)],
569            result_type: Some(ResultType::Complete),
570            ..Self::default()
571        }
572    }
573
574    /// A result carrying both the model-facing content and the program-facing
575    /// structured value produced by a [`ToolOutput`](crate::traits::ToolOutput).
576    pub fn from_content(content: Vec<ContentBlock>, structured_content: Option<Value>) -> Self {
577        Self {
578            content,
579            structured_content,
580            result_type: Some(ResultType::Complete),
581            ..Self::default()
582        }
583    }
584
585    pub fn error(text: impl Into<String>) -> Self {
586        Self {
587            is_error: Some(true),
588            ..Self::text(text)
589        }
590    }
591}
592
593/// An interim `tools/call`, `prompts/get`, or `resources/read` result: the server needs more input
594/// before it can finish, and the client is expected to supply it and re-issue the original request
595/// as a *new* request.
596///
597/// This crate's serve loop never produces one but a client must be able to recognize one. At least
598/// one of `input_requests` or `request_state` is always present; a `request_state`-only result is
599/// the spec's load-shedding case and requires no declared client capability, so *any* client can
600/// receive one.
601#[derive(Debug, Clone, Serialize, Deserialize)]
602#[serde(rename_all = "camelCase")]
603pub struct InputRequiredResult {
604    /// Server-initiated requests the client must fulfill, keyed by
605    /// server-assigned identifiers. Values are `elicitation/create`,
606    /// `sampling/createMessage`, or `roots/list` requests; the latter two are
607    /// deprecated in `2026-07-28`.
608    #[serde(skip_serializing_if = "Option::is_none")]
609    pub input_requests: Option<Map<String, Value>>,
610    /// Opaque server state to echo back verbatim on the retry. Clients MUST
611    /// NOT inspect, parse, or modify it, and MUST NOT invent one when the
612    /// server did not send one.
613    #[serde(skip_serializing_if = "Option::is_none")]
614    pub request_state: Option<String>,
615    #[serde(skip_serializing_if = "Option::is_none")]
616    pub result_type: Option<ResultType>,
617    #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
618    pub meta: Option<Map<String, Value>>,
619}
620
621/// The two shapes a successful `tools/call` response can take.
622///
623/// Discriminated on `resultType`, treating absent as
624/// [`Complete`](ResultType::Complete) per the spec's rule for servers on
625/// earlier revisions. Without this discrimination a client would deserialize an
626/// [`InputRequiredResult`] as a `CallToolResult` with empty `content` and
627/// report a successful empty tool call.
628#[derive(Debug, Clone, Serialize)]
629#[serde(untagged)]
630pub enum ToolCallOutcome {
631    Complete(CallToolResult),
632    InputRequired(InputRequiredResult),
633}
634
635impl<'de> Deserialize<'de> for ToolCallOutcome {
636    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
637    where
638        D: Deserializer<'de>,
639    {
640        use serde::de::Error;
641        let value = Value::deserialize(deserializer)?;
642        let result_type = value.get("resultType").and_then(Value::as_str);
643        if result_type == Some("input_required") {
644            serde_json::from_value(value).map(Self::InputRequired)
645        } else {
646            serde_json::from_value(value).map(Self::Complete)
647        }
648        .map_err(D::Error::custom)
649    }
650}
651
652// --- request context ---
653
654/// What a request tells a tool about its caller.
655///
656/// `2026-07-28` removed the handshake, so protocol version and client
657/// capabilities arrive in every request's `_meta` instead of once at
658/// initialization. Capabilities are per-request by design: the spec forbids
659/// servers from inferring them from prior requests, so this is rebuilt for
660/// each call rather than cached.
661///
662/// Requests from earlier revisions carry none of this; every field is
663/// therefore optional or defaulted.
664#[derive(Debug, Clone, Default)]
665pub struct RequestContext {
666    /// The protocol version the caller declared for this request.
667    pub protocol_version: Option<String>,
668    /// The caller's self-reported identity. Not verified by the protocol —
669    /// for display, logging, and debugging only. Servers SHOULD NOT change
670    /// behavior based on it, and MUST NOT use it for security decisions.
671    pub client_info: Option<Implementation>,
672    /// What the caller supports *for this request*. Empty means no optional
673    /// capabilities.
674    pub client_capabilities: ClientCapabilities,
675}
676
677impl RequestContext {
678    /// Build a context from a request's `_meta` object.
679    pub fn from_meta(meta: Option<&Map<String, Value>>) -> Self {
680        let Some(meta) = meta else {
681            return Self::default();
682        };
683
684        Self {
685            protocol_version: meta
686                .get(meta_keys::PROTOCOL_VERSION)
687                .and_then(Value::as_str)
688                .map(String::from),
689            client_info: meta
690                .get(meta_keys::CLIENT_INFO)
691                .cloned()
692                .and_then(|value| serde_json::from_value(value).ok()),
693            client_capabilities: meta
694                .get(meta_keys::CLIENT_CAPABILITIES)
695                .cloned()
696                .and_then(|value| serde_json::from_value(value).ok())
697                .unwrap_or_default(),
698        }
699    }
700
701    /// Pull the `_meta` out of a request's `params` and build a context.
702    pub fn from_params(params: Option<&Value>) -> Self {
703        Self::from_meta(
704            params
705                .and_then(|params| params.get("_meta"))
706                .and_then(Value::as_object),
707        )
708    }
709
710    /// Whether the caller declared support for elicitation on this request.
711    /// A server MUST NOT send an elicitation `inputRequest` when this is false.
712    pub fn supports_elicitation(&self) -> bool {
713        self.client_capabilities.elicitation.is_some()
714    }
715}
716
717// --- content ---
718
719/// One block of tool-result or prompt content, discriminated by `type`.
720#[derive(Debug, Clone, Serialize, Deserialize)]
721#[serde(tag = "type")]
722pub enum ContentBlock {
723    #[serde(rename = "text", rename_all = "camelCase")]
724    Text {
725        text: String,
726        #[serde(skip_serializing_if = "Option::is_none")]
727        annotations: Option<Annotations>,
728        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
729        meta: Option<Map<String, Value>>,
730    },
731    #[serde(rename = "image", rename_all = "camelCase")]
732    Image {
733        /// Base64-encoded image data.
734        data: String,
735        mime_type: String,
736        #[serde(skip_serializing_if = "Option::is_none")]
737        annotations: Option<Annotations>,
738        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
739        meta: Option<Map<String, Value>>,
740    },
741    #[serde(rename = "audio", rename_all = "camelCase")]
742    Audio {
743        /// Base64-encoded audio data.
744        data: String,
745        mime_type: String,
746        #[serde(skip_serializing_if = "Option::is_none")]
747        annotations: Option<Annotations>,
748        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
749        meta: Option<Map<String, Value>>,
750    },
751    #[serde(rename = "resource_link", rename_all = "camelCase")]
752    ResourceLink {
753        uri: String,
754        name: String,
755        #[serde(skip_serializing_if = "Option::is_none")]
756        title: Option<String>,
757        #[serde(skip_serializing_if = "Option::is_none")]
758        description: Option<String>,
759        #[serde(skip_serializing_if = "Option::is_none")]
760        mime_type: Option<String>,
761        #[serde(skip_serializing_if = "Option::is_none")]
762        size: Option<u64>,
763        #[serde(skip_serializing_if = "Option::is_none")]
764        icons: Option<Vec<Icon>>,
765        #[serde(skip_serializing_if = "Option::is_none")]
766        annotations: Option<Annotations>,
767        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
768        meta: Option<Map<String, Value>>,
769    },
770    #[serde(rename = "resource", rename_all = "camelCase")]
771    EmbeddedResource {
772        resource: ResourceContents,
773        #[serde(skip_serializing_if = "Option::is_none")]
774        annotations: Option<Annotations>,
775        #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
776        meta: Option<Map<String, Value>>,
777    },
778}
779
780impl ContentBlock {
781    pub fn text(text: impl Into<String>) -> Self {
782        Self::Text {
783            text: text.into(),
784            annotations: None,
785            meta: None,
786        }
787    }
788}
789
790/// The contents of an embedded resource: textual or binary.
791#[derive(Debug, Clone, Serialize, Deserialize)]
792#[serde(untagged)]
793pub enum ResourceContents {
794    #[serde(rename_all = "camelCase")]
795    Text {
796        uri: String,
797        #[serde(skip_serializing_if = "Option::is_none")]
798        mime_type: Option<String>,
799        text: String,
800    },
801    #[serde(rename_all = "camelCase")]
802    Blob {
803        uri: String,
804        #[serde(skip_serializing_if = "Option::is_none")]
805        mime_type: Option<String>,
806        /// Base64-encoded binary data.
807        blob: String,
808    },
809}
810
811/// Client-facing annotations on a content block.
812#[derive(Debug, Clone, Default, Serialize, Deserialize)]
813#[serde(rename_all = "camelCase")]
814pub struct Annotations {
815    #[serde(skip_serializing_if = "Option::is_none")]
816    pub audience: Option<Vec<String>>,
817    #[serde(skip_serializing_if = "Option::is_none")]
818    pub priority: Option<f64>,
819    #[serde(skip_serializing_if = "Option::is_none")]
820    pub last_modified: Option<String>,
821}
822
823// --- framework support ---
824
825/// A described example attached to a tool's schema (`examples` keyword).
826#[derive(Serialize, Deserialize, Debug)]
827pub struct Example<T> {
828    pub description: &'static str,
829    #[serde(flatten)]
830    pub item: T,
831}