// Internal references to the SEP-2577-deprecated Roots/Sampling/Logging types // defined in this module are expected; the deprecation is advisory for downstream users. #![expect(deprecated)] use std::{ borrow::Cow, ops::{Deref, DerefMut}, sync::Arc, }; mod annotated; mod capabilities; mod content; mod elicitation_schema; mod extension; mod meta; mod prompt; mod resource; mod serde_impl; mod task; mod tool; pub use annotated::*; pub use capabilities::*; pub use content::*; pub use elicitation_schema::*; pub use extension::*; pub use meta::*; pub use prompt::*; pub use resource::*; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use serde_json::Value; pub use task::*; pub use tool::*; /// A JSON object type alias for convenient handling of JSON data. /// /// You can use [`crate::object!`] or [`crate::model::object`] to create a json object quickly. /// This is commonly used for storing arbitrary JSON data in MCP messages. pub type JsonObject = serde_json::Map; /// unwrap the JsonObject under [`serde_json::Value`] /// /// # Panic /// This will panic when the value is not a object in debug mode. pub fn object(value: serde_json::Value) -> JsonObject { debug_assert!(value.is_object()); match value { serde_json::Value::Object(map) => map, _ => JsonObject::default(), } } /// Use this macro just like [`serde_json::json!`] #[cfg(feature = "macros")] #[macro_export] macro_rules! object { ({$($tt:tt)*}) => { $crate::model::object(serde_json::json! { {$($tt)*} }) }; } /// This is commonly used for representing empty objects in MCP messages. /// /// without returning any specific data. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy, Eq)] #[serde(deny_unknown_fields)] #[cfg_attr(feature = "server", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct EmptyObject {} pub trait ConstString: Default { const VALUE: &str; fn as_str(&self) -> &'static str { Self::VALUE } } #[macro_export] macro_rules! const_string { ($name:ident = $value:literal) => { #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct $name; impl ConstString for $name { const VALUE: &str = $value; } impl serde::Serialize for $name { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { $value.serialize(serializer) } } impl<'de> serde::Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result<$name, D::Error> where D: serde::Deserializer<'de>, { let s: String = serde::Deserialize::deserialize(deserializer)?; if s == $value { Ok($name) } else { Err(serde::de::Error::custom(format!(concat!( "expect const string value \"", $value, "\"" )))) } } } #[cfg(feature = "schemars")] impl schemars::JsonSchema for $name { fn schema_name() -> Cow<'static, str> { Cow::Borrowed(stringify!($name)) } fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { use serde_json::{Map, json}; let mut schema_map = Map::new(); schema_map.insert("type".to_string(), json!("string")); schema_map.insert("format".to_string(), json!("const")); schema_map.insert("const".to_string(), json!($value)); schemars::Schema::from(schema_map) } } }; } const_string!(JsonRpcVersion2_0 = "2.0"); // ============================================================================= // CORE PROTOCOL TYPES // ============================================================================= /// Represents the MCP protocol version used for communication. /// /// This ensures compatibility between clients and servers by specifying /// which version of the Model Context Protocol is being used. #[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ProtocolVersion(Cow<'static, str>); impl Default for ProtocolVersion { fn default() -> Self { Self::LATEST } } impl std::fmt::Display for ProtocolVersion { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } impl ProtocolVersion { pub const V_2026_07_28: Self = Self(Cow::Borrowed("2026-07-28")); pub const V_2025_11_25: Self = Self(Cow::Borrowed("2025-11-25")); pub const V_2025_06_18: Self = Self(Cow::Borrowed("2025-06-18")); pub const V_2025_03_26: Self = Self(Cow::Borrowed("2025-03-26")); pub const V_2024_11_05: Self = Self(Cow::Borrowed("2024-11-05")); pub const LATEST: Self = Self::V_2025_11_25; /// All protocol versions known to this SDK. pub const KNOWN_VERSIONS: &[Self] = &[ Self::V_2024_11_05, Self::V_2025_03_26, Self::V_2025_06_18, Self::V_2025_11_25, Self::V_2026_07_28, ]; /// Returns the string representation of this protocol version. pub fn as_str(&self) -> &str { &self.0 } } impl Serialize for ProtocolVersion { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { self.0.serialize(serializer) } } impl<'de> Deserialize<'de> for ProtocolVersion { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let s: String = Deserialize::deserialize(deserializer)?; #[allow(clippy::single_match)] match s.as_str() { "2024-11-05" => return Ok(ProtocolVersion::V_2024_11_05), "2025-03-26" => return Ok(ProtocolVersion::V_2025_03_26), "2025-06-18" => return Ok(ProtocolVersion::V_2025_06_18), "2025-11-25" => return Ok(ProtocolVersion::V_2025_11_25), "2026-07-28" => return Ok(ProtocolVersion::V_2026_07_28), _ => {} } Ok(ProtocolVersion(Cow::Owned(s))) } } /// A flexible identifier type that can be either a number or a string. /// /// This is commonly used for request IDs and other identifiers in JSON-RPC /// where the specification allows both numeric and string values. #[derive(Debug, Clone, Eq, PartialEq, Hash)] #[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")] pub enum NumberOrString { /// A numeric identifier Number(i64), /// A string identifier String(Arc), } impl NumberOrString { pub fn into_json_value(self) -> Value { match self { NumberOrString::Number(n) => Value::Number(serde_json::Number::from(n)), NumberOrString::String(s) => Value::String(s.to_string()), } } } impl std::fmt::Display for NumberOrString { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { NumberOrString::Number(n) => n.fmt(f), NumberOrString::String(s) => s.fmt(f), } } } impl Serialize for NumberOrString { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { match self { NumberOrString::Number(n) => n.serialize(serializer), NumberOrString::String(s) => s.serialize(serializer), } } } impl<'de> Deserialize<'de> for NumberOrString { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let value: Value = Deserialize::deserialize(deserializer)?; match value { Value::Number(n) => { if let Some(i) = n.as_i64() { Ok(NumberOrString::Number(i)) } else if let Some(u) = n.as_u64() { // Handle large unsigned numbers that fit in i64 if u <= i64::MAX as u64 { Ok(NumberOrString::Number(u as i64)) } else { Err(serde::de::Error::custom("Number too large for i64")) } } else { Err(serde::de::Error::custom("Expected an integer")) } } Value::String(s) => Ok(NumberOrString::String(s.into())), _ => Err(serde::de::Error::custom("Expect number or string")), } } } #[cfg(feature = "schemars")] impl schemars::JsonSchema for NumberOrString { fn schema_name() -> Cow<'static, str> { Cow::Borrowed("NumberOrString") } fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { use serde_json::{Map, json}; let mut number_schema = Map::new(); number_schema.insert("type".to_string(), json!("number")); let mut string_schema = Map::new(); string_schema.insert("type".to_string(), json!("string")); let mut schema_map = Map::new(); schema_map.insert("oneOf".to_string(), json!([number_schema, string_schema])); schemars::Schema::from(schema_map) } } /// Type alias for request identifiers used in JSON-RPC communication. pub type RequestId = NumberOrString; /// A token used to track the progress of long-running operations. /// /// Progress tokens allow clients and servers to associate progress notifications /// with specific requests, enabling real-time updates on operation status. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Hash, Eq)] #[serde(transparent)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct ProgressToken(pub NumberOrString); // ============================================================================= // JSON-RPC MESSAGE STRUCTURES // ============================================================================= /// Represents a JSON-RPC request with method, parameters, and extensions. /// /// This is the core structure for all MCP requests, containing: /// - `method`: The name of the method being called /// - `params`: The parameters for the method /// - `extensions`: Additional context data (similar to HTTP headers) #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct Request { pub method: M, pub params: P, /// extensions will carry anything possible in the context, including [`Meta`] /// /// this is similar with the Extensions in `http` crate #[cfg_attr(feature = "schemars", schemars(skip))] pub extensions: Extensions, } impl Request { pub fn new(params: P) -> Self { Self { method: Default::default(), params, extensions: Extensions::default(), } } } impl GetExtensions for Request { fn extensions(&self) -> &Extensions { &self.extensions } fn extensions_mut(&mut self) -> &mut Extensions { &mut self.extensions } } #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct RequestOptionalParam { pub method: M, // #[serde(skip_serializing_if = "Option::is_none")] pub params: Option

, /// extensions will carry anything possible in the context, including [`Meta`] /// /// this is similar with the Extensions in `http` crate #[cfg_attr(feature = "schemars", schemars(skip))] pub extensions: Extensions, } impl RequestOptionalParam { pub fn with_param(params: P) -> Self { Self { method: Default::default(), params: Some(params), extensions: Extensions::default(), } } } #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct RequestNoParam { pub method: M, /// extensions will carry anything possible in the context, including [`Meta`] /// /// this is similar with the Extensions in `http` crate #[cfg_attr(feature = "schemars", schemars(skip))] pub extensions: Extensions, } impl GetExtensions for RequestNoParam { fn extensions(&self) -> &Extensions { &self.extensions } fn extensions_mut(&mut self) -> &mut Extensions { &mut self.extensions } } #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct Notification { pub method: M, pub params: P, /// extensions will carry anything possible in the context, including [`Meta`] /// /// this is similar with the Extensions in `http` crate #[cfg_attr(feature = "schemars", schemars(skip))] pub extensions: Extensions, } impl Notification { pub fn new(params: P) -> Self { Self { method: Default::default(), params, extensions: Extensions::default(), } } } #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct NotificationNoParam { pub method: M, /// extensions will carry anything possible in the context, including [`Meta`] /// /// this is similar with the Extensions in `http` crate #[cfg_attr(feature = "schemars", schemars(skip))] pub extensions: Extensions, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct JsonRpcRequest { pub jsonrpc: JsonRpcVersion2_0, pub id: RequestId, #[serde(flatten)] pub request: R, } impl JsonRpcRequest { /// Create a new JsonRpcRequest. pub fn new(id: RequestId, request: R) -> Self { Self { jsonrpc: JsonRpcVersion2_0, id, request, } } } type DefaultResponse = JsonObject; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct JsonRpcResponse { pub jsonrpc: JsonRpcVersion2_0, pub id: RequestId, pub result: R, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct JsonRpcError { pub jsonrpc: JsonRpcVersion2_0, // MCP 2025-11-25 §Error Responses: `id` is optional and omitted when the // server cannot read the request id (e.g. parse error / invalid request). // https://modelcontextprotocol.io/specification/2025-11-25/basic#error-responses #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option, pub error: ErrorData, } impl JsonRpcError { /// Create a new JsonRpcError. pub fn new(id: Option, error: ErrorData) -> Self { Self { jsonrpc: JsonRpcVersion2_0, id, error, } } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct JsonRpcNotification { pub jsonrpc: JsonRpcVersion2_0, #[serde(flatten)] pub notification: N, } /// Standard JSON-RPC error codes used throughout the MCP protocol. /// /// These codes follow the JSON-RPC 2.0 specification and provide /// standardized error reporting across all MCP implementations. #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(transparent)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct ErrorCode(pub i32); impl ErrorCode { pub const RESOURCE_NOT_FOUND: Self = Self(-32002); pub const INVALID_REQUEST: Self = Self(-32600); pub const METHOD_NOT_FOUND: Self = Self(-32601); pub const INVALID_PARAMS: Self = Self(-32602); pub const INTERNAL_ERROR: Self = Self(-32603); pub const PARSE_ERROR: Self = Self(-32700); pub const URL_ELICITATION_REQUIRED: Self = Self(-32042); } /// Error information for JSON-RPC error responses. /// /// This structure follows the JSON-RPC 2.0 specification for error reporting, /// providing a standardized way to communicate errors between clients and servers. #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct ErrorData { /// The error type that occurred (using standard JSON-RPC error codes) pub code: ErrorCode, /// A short description of the error. The message SHOULD be limited to a concise single sentence. pub message: Cow<'static, str>, /// Additional information about the error. The value of this member is defined by the /// sender (e.g. detailed error information, nested errors etc.). #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, } impl ErrorData { pub fn new( code: ErrorCode, message: impl Into>, data: Option, ) -> Self { Self { code, message: message.into(), data, } } /// Resource-not-found error (`-32002`). The server upgrades this to `INVALID_PARAMS` /// (`-32602`) for peers negotiating protocol `2026-07-28` or newer (SEP-2164). pub fn resource_not_found(message: impl Into>, data: Option) -> Self { Self::new(ErrorCode::RESOURCE_NOT_FOUND, message, data) } pub fn parse_error(message: impl Into>, data: Option) -> Self { Self::new(ErrorCode::PARSE_ERROR, message, data) } pub fn invalid_request(message: impl Into>, data: Option) -> Self { Self::new(ErrorCode::INVALID_REQUEST, message, data) } pub fn method_not_found() -> Self { Self::new(ErrorCode::METHOD_NOT_FOUND, M::VALUE, None) } pub fn invalid_params(message: impl Into>, data: Option) -> Self { Self::new(ErrorCode::INVALID_PARAMS, message, data) } pub fn internal_error(message: impl Into>, data: Option) -> Self { Self::new(ErrorCode::INTERNAL_ERROR, message, data) } pub fn url_elicitation_required( message: impl Into>, data: Option, ) -> Self { Self::new(ErrorCode::URL_ELICITATION_REQUIRED, message, data) } } /// Represents any JSON-RPC message that can be sent or received. /// /// This enum covers all possible message types in the JSON-RPC protocol: /// individual requests/responses, notifications, and errors. /// It serves as the top-level message container for MCP communication. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(untagged)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")] pub enum JsonRpcMessage { /// A single request expecting a response Request(JsonRpcRequest), /// A response to a previous request Response(JsonRpcResponse), /// A one-way notification (no response expected) Notification(JsonRpcNotification), /// An error response Error(JsonRpcError), } impl JsonRpcMessage { #[inline] pub const fn request(request: Req, id: RequestId) -> Self { JsonRpcMessage::Request(JsonRpcRequest { jsonrpc: JsonRpcVersion2_0, id, request, }) } #[inline] pub const fn response(response: Resp, id: RequestId) -> Self { JsonRpcMessage::Response(JsonRpcResponse { jsonrpc: JsonRpcVersion2_0, id, result: response, }) } #[inline] pub const fn error(error: ErrorData, id: Option) -> Self { JsonRpcMessage::Error(JsonRpcError { jsonrpc: JsonRpcVersion2_0, id, error, }) } #[inline] pub const fn notification(notification: Not) -> Self { JsonRpcMessage::Notification(JsonRpcNotification { jsonrpc: JsonRpcVersion2_0, notification, }) } pub fn into_request(self) -> Option<(Req, RequestId)> { match self { JsonRpcMessage::Request(r) => Some((r.request, r.id)), _ => None, } } pub fn into_response(self) -> Option<(Resp, RequestId)> { match self { JsonRpcMessage::Response(r) => Some((r.result, r.id)), _ => None, } } pub fn into_notification(self) -> Option { match self { JsonRpcMessage::Notification(n) => Some(n.notification), _ => None, } } pub fn into_error(self) -> Option<(ErrorData, Option)> { match self { JsonRpcMessage::Error(e) => Some((e.error, e.id)), _ => None, } } pub fn into_result(self) -> Option<(Result, Option)> { match self { JsonRpcMessage::Response(r) => Some((Ok(r.result), Some(r.id))), JsonRpcMessage::Error(e) => Some((Err(e.error), e.id)), _ => None, } } } // ============================================================================= // INITIALIZATION AND CONNECTION SETUP // ============================================================================= /// # Empty result /// A response that indicates success but carries no data. pub type EmptyResult = EmptyObject; impl From<()> for EmptyResult { fn from(_value: ()) -> Self { EmptyResult {} } } impl From for () { fn from(_value: EmptyResult) {} } /// A catch-all response either side can use for custom requests. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(transparent)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct CustomResult(pub Value); impl CustomResult { pub fn new(result: Value) -> Self { Self(result) } /// Deserialize the result into a strongly-typed structure. pub fn result_as(&self) -> Result { serde_json::from_value(self.0.clone()) } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct CancelledNotificationParam { #[serde(skip_serializing_if = "Option::is_none")] pub request_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl CancelledNotificationParam { pub fn new(request_id: Option, reason: Option) -> Self { Self { request_id, reason, meta: None, } } } const_string!(CancelledNotificationMethod = "notifications/cancelled"); /// # Cancellation /// This notification can be sent by either side to indicate that it is cancelling a previously-issued request. /// /// The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. /// /// This notification indicates that the result will be unused, so any associated processing SHOULD cease. /// /// A client MUST NOT attempt to cancel its `initialize` request. pub type CancelledNotification = Notification; /// A catch-all notification either side can use to send custom messages to its peer. /// /// This preserves the raw `method` name and `params` payload so handlers can /// deserialize them into domain-specific types. #[derive(Debug, Clone)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct CustomNotification { pub method: String, pub params: Option, /// extensions will carry anything possible in the context, including [`Meta`] /// /// this is similar with the Extensions in `http` crate #[cfg_attr(feature = "schemars", schemars(skip))] pub extensions: Extensions, } impl CustomNotification { pub fn new(method: impl Into, params: Option) -> Self { Self { method: method.into(), params, extensions: Extensions::default(), } } /// Deserialize `params` into a strongly-typed structure. pub fn params_as(&self) -> Result, serde_json::Error> { self.params .as_ref() .map(|params| serde_json::from_value(params.clone())) .transpose() } } /// A catch-all request either side can use to send custom messages to its peer. /// /// This preserves the raw `method` name and `params` payload so handlers can /// deserialize them into domain-specific types. #[derive(Debug, Clone)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct CustomRequest { pub method: String, pub params: Option, /// extensions will carry anything possible in the context, including [`Meta`] /// /// this is similar with the Extensions in `http` crate #[cfg_attr(feature = "schemars", schemars(skip))] pub extensions: Extensions, } impl CustomRequest { pub fn new(method: impl Into, params: Option) -> Self { Self { method: method.into(), params, extensions: Extensions::default(), } } /// Deserialize `params` into a strongly-typed structure. pub fn params_as(&self) -> Result, serde_json::Error> { self.params .as_ref() .map(|params| serde_json::from_value(params.clone())) .transpose() } } const_string!(InitializeResultMethod = "initialize"); /// # Initialization /// This request is sent from the client to the server when it first connects, asking it to begin initialization. pub type InitializeRequest = Request; const_string!(InitializedNotificationMethod = "notifications/initialized"); /// This notification is sent from the client to the server after initialization has finished. pub type InitializedNotification = NotificationNoParam; /// Parameters sent by a client when initializing a connection to an MCP server. /// /// This contains the client's protocol version, capabilities, and implementation /// information, allowing the server to understand what the client supports. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct InitializeRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, /// The MCP protocol version this client supports pub protocol_version: ProtocolVersion, /// The capabilities this client supports (sampling, roots, etc.) pub capabilities: ClientCapabilities, /// Information about the client implementation pub client_info: Implementation, } impl InitializeRequestParams { /// Create a new InitializeRequestParams. pub fn new(capabilities: ClientCapabilities, client_info: Implementation) -> Self { Self { meta: None, protocol_version: ProtocolVersion::default(), capabilities, client_info, } } pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self { self.protocol_version = protocol_version; self } } impl RequestParamsMeta for InitializeRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } /// Deprecated: Use [`InitializeRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use InitializeRequestParams instead")] pub type InitializeRequestParam = InitializeRequestParams; /// The server's response to an initialization request. /// /// Contains the server's protocol version, capabilities, and implementation /// information, along with optional instructions for the client. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct InitializeResult { /// The MCP protocol version this server supports pub protocol_version: ProtocolVersion, /// The capabilities this server provides (tools, resources, prompts, etc.) pub capabilities: ServerCapabilities, /// Information about the server implementation pub server_info: Implementation, /// Optional human-readable instructions about using this server #[serde(skip_serializing_if = "Option::is_none")] pub instructions: Option, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl InitializeResult { /// Create a new `InitializeResult` with default protocol version and the given capabilities. pub fn new(capabilities: ServerCapabilities) -> Self { Self { protocol_version: ProtocolVersion::default(), capabilities, server_info: Implementation::from_build_env(), instructions: None, meta: None, } } /// Set instructions on this result. pub fn with_instructions(mut self, instructions: impl Into) -> Self { self.instructions = Some(instructions.into()); self } /// Set the server info on this result. pub fn with_server_info(mut self, server_info: Implementation) -> Self { self.server_info = server_info; self } /// Set the protocol version on this result. pub fn with_protocol_version(mut self, protocol_version: ProtocolVersion) -> Self { self.protocol_version = protocol_version; self } } pub type ServerInfo = InitializeResult; pub type ClientInfo = InitializeRequestParams; #[allow(clippy::derivable_impls)] impl Default for ServerInfo { fn default() -> Self { ServerInfo { protocol_version: ProtocolVersion::default(), capabilities: ServerCapabilities::default(), server_info: Implementation::from_build_env(), instructions: None, meta: None, } } } #[allow(clippy::derivable_impls)] impl Default for ClientInfo { fn default() -> Self { ClientInfo { meta: None, protocol_version: ProtocolVersion::default(), capabilities: ClientCapabilities::default(), client_info: Implementation::from_build_env(), } } } /// Icon themes supported by the MCP specification #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Copy)] #[serde(rename_all = "lowercase")] //match spec #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub enum IconTheme { /// Indicates the icon is designed to be used with a light background Light, /// Indicates the icon is designed to be used with a dark background Dark, } /// A URL pointing to an icon resource or a base64-encoded data URI. /// /// Clients that support rendering icons MUST support at least the following MIME types: /// - image/png - PNG images (safe, universal compatibility) /// - image/jpeg (and image/jpg) - JPEG images (safe, universal compatibility) /// /// Clients that support rendering icons SHOULD also support: /// - image/svg+xml - SVG images (scalable but requires security precautions) /// - image/webp - WebP images (modern, efficient format) #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct Icon { /// A standard URI pointing to an icon resource pub src: String, /// Optional override if the server's MIME type is missing or generic #[serde(skip_serializing_if = "Option::is_none")] pub mime_type: Option, /// Size specification, each string should be in WxH format (e.g., `\"48x48\"`, `\"96x96\"`) or `\"any\"` for scalable formats like SVG #[serde(skip_serializing_if = "Option::is_none")] pub sizes: Option>, /// Optional specifier for the theme this icon is designed for /// If not provided, the client should assume the icon can be used with any theme. #[serde(skip_serializing_if = "Option::is_none")] pub theme: Option, } impl Icon { /// Create a new Icon with the given source URL. pub fn new(src: impl Into) -> Self { Self { src: src.into(), mime_type: None, sizes: None, theme: None, } } /// Set the MIME type. pub fn with_mime_type(mut self, mime_type: impl Into) -> Self { self.mime_type = Some(mime_type.into()); self } /// Set the sizes. pub fn with_sizes(mut self, sizes: Vec) -> Self { self.sizes = Some(sizes); self } /// Set the theme. pub fn with_theme(mut self, theme: IconTheme) -> Self { self.theme = Some(theme); self } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct Implementation { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, pub version: String, #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, #[serde(skip_serializing_if = "Option::is_none")] pub icons: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub website_url: Option, } impl Default for Implementation { fn default() -> Self { Self::from_build_env() } } impl Implementation { /// Create a new Implementation. pub fn new(name: impl Into, version: impl Into) -> Self { Self { name: name.into(), title: None, version: version.into(), description: None, icons: None, website_url: None, } } pub fn from_build_env() -> Self { Implementation { name: env!("CARGO_CRATE_NAME").to_owned(), title: None, version: env!("CARGO_PKG_VERSION").to_owned(), description: None, icons: None, website_url: None, } } /// Set the human-readable title. pub fn with_title(mut self, title: impl Into) -> Self { self.title = Some(title.into()); self } /// Set the description. pub fn with_description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } /// Set the icons. pub fn with_icons(mut self, icons: Vec) -> Self { self.icons = Some(icons); self } /// Set the website URL. pub fn with_website_url(mut self, website_url: impl Into) -> Self { self.website_url = Some(website_url.into()); self } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct PaginatedRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, #[serde(skip_serializing_if = "Option::is_none")] pub cursor: Option, } impl PaginatedRequestParams { pub fn with_cursor(mut self, cursor: Option) -> Self { self.cursor = cursor; self } } impl RequestParamsMeta for PaginatedRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } /// Deprecated: Use [`PaginatedRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use PaginatedRequestParams instead")] pub type PaginatedRequestParam = PaginatedRequestParams; // ============================================================================= // PROGRESS AND PAGINATION // ============================================================================= const_string!(PingRequestMethod = "ping"); pub type PingRequest = RequestNoParam; const_string!(ProgressNotificationMethod = "notifications/progress"); #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct ProgressNotificationParam { pub progress_token: ProgressToken, /// The progress thus far. This should increase every time progress is made, even if the total is unknown. pub progress: f64, /// Total number of items to process (or total progress required), if known #[serde(skip_serializing_if = "Option::is_none")] pub total: Option, /// An optional message describing the current progress. #[serde(skip_serializing_if = "Option::is_none")] pub message: Option, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl ProgressNotificationParam { /// Create a new ProgressNotificationParam with required fields. pub fn new(progress_token: ProgressToken, progress: f64) -> Self { Self { progress_token, progress, total: None, message: None, meta: None, } } /// Set the total number of items to process. pub fn with_total(mut self, total: f64) -> Self { self.total = Some(total); self } /// Set a message describing the current progress. pub fn with_message(mut self, message: impl Into) -> Self { self.message = Some(message.into()); self } } pub type ProgressNotification = Notification; pub type Cursor = String; macro_rules! paginated_result { ($t:ident { $i_item: ident: $t_item: ty }) => { #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")] pub struct $t { #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, #[serde(skip_serializing_if = "Option::is_none")] pub next_cursor: Option, pub $i_item: $t_item, } impl $t { pub fn with_all_items( items: $t_item, ) -> Self { Self { meta: None, next_cursor: None, $i_item: items, } } } }; } // ============================================================================= // RESOURCE MANAGEMENT // ============================================================================= const_string!(ListResourcesRequestMethod = "resources/list"); /// Request to list all available resources from a server pub type ListResourcesRequest = RequestOptionalParam; paginated_result!(ListResourcesResult { resources: Vec }); const_string!(ListResourceTemplatesRequestMethod = "resources/templates/list"); /// Request to list all available resource templates from a server pub type ListResourceTemplatesRequest = RequestOptionalParam; paginated_result!(ListResourceTemplatesResult { resource_templates: Vec }); const_string!(ReadResourceRequestMethod = "resources/read"); /// Parameters for reading a specific resource #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct ReadResourceRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, /// The URI of the resource to read pub uri: String, } impl ReadResourceRequestParams { /// Create a new ReadResourceRequestParams with the given URI. pub fn new(uri: impl Into) -> Self { Self { meta: None, uri: uri.into(), } } /// Set the metadata for this request. pub fn with_meta(mut self, meta: Meta) -> Self { self.meta = Some(meta); self } } impl RequestParamsMeta for ReadResourceRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } /// Deprecated: Use [`ReadResourceRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use ReadResourceRequestParams instead")] pub type ReadResourceRequestParam = ReadResourceRequestParams; /// Result containing the contents of a read resource #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct ReadResourceResult { /// The actual content of the resource pub contents: Vec, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl ReadResourceResult { /// Create a new ReadResourceResult with the given contents. pub fn new(contents: Vec) -> Self { Self { contents, meta: None, } } } /// Request to read a specific resource pub type ReadResourceRequest = Request; const_string!(ResourceListChangedNotificationMethod = "notifications/resources/list_changed"); /// Notification sent when the list of available resources changes pub type ResourceListChangedNotification = NotificationNoParam; const_string!(SubscribeRequestMethod = "resources/subscribe"); /// Parameters for subscribing to resource updates #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct SubscribeRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, /// The URI of the resource to subscribe to pub uri: String, } impl SubscribeRequestParams { /// Create a new SubscribeRequestParams. pub fn new(uri: impl Into) -> Self { Self { meta: None, uri: uri.into(), } } } impl RequestParamsMeta for SubscribeRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } /// Deprecated: Use [`SubscribeRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use SubscribeRequestParams instead")] pub type SubscribeRequestParam = SubscribeRequestParams; /// Request to subscribe to resource updates pub type SubscribeRequest = Request; const_string!(UnsubscribeRequestMethod = "resources/unsubscribe"); /// Parameters for unsubscribing from resource updates #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct UnsubscribeRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, /// The URI of the resource to unsubscribe from pub uri: String, } impl UnsubscribeRequestParams { /// Creates a new `UnsubscribeRequestParams` for the given URI. pub fn new(uri: impl Into) -> Self { Self { meta: None, uri: uri.into(), } } } impl RequestParamsMeta for UnsubscribeRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } /// Deprecated: Use [`UnsubscribeRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use UnsubscribeRequestParams instead")] pub type UnsubscribeRequestParam = UnsubscribeRequestParams; /// Request to unsubscribe from resource updates pub type UnsubscribeRequest = Request; const_string!(ResourceUpdatedNotificationMethod = "notifications/resources/updated"); /// Parameters for a resource update notification #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct ResourceUpdatedNotificationParam { /// The URI of the resource that was updated pub uri: String, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl ResourceUpdatedNotificationParam { /// Create a new ResourceUpdatedNotificationParam. pub fn new(uri: impl Into) -> Self { Self { uri: uri.into(), meta: None, } } } /// Notification sent when a subscribed resource is updated pub type ResourceUpdatedNotification = Notification; // ============================================================================= // PROMPT MANAGEMENT // ============================================================================= const_string!(ListPromptsRequestMethod = "prompts/list"); /// Request to list all available prompts from a server pub type ListPromptsRequest = RequestOptionalParam; paginated_result!(ListPromptsResult { prompts: Vec }); const_string!(GetPromptRequestMethod = "prompts/get"); /// Parameters for retrieving a specific prompt #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct GetPromptRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub arguments: Option, } impl GetPromptRequestParams { /// Create a new `GetPromptRequestParams` with the given prompt name. pub fn new(name: impl Into) -> Self { Self { meta: None, name: name.into(), arguments: None, } } /// Set the arguments for this prompt request. pub fn with_arguments(mut self, arguments: JsonObject) -> Self { self.arguments = Some(arguments); self } /// Set the metadata for this request. pub fn with_meta(mut self, meta: Meta) -> Self { self.meta = Some(meta); self } } impl RequestParamsMeta for GetPromptRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } /// Deprecated: Use [`GetPromptRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use GetPromptRequestParams instead")] pub type GetPromptRequestParam = GetPromptRequestParams; /// Request to get a specific prompt pub type GetPromptRequest = Request; const_string!(PromptListChangedNotificationMethod = "notifications/prompts/list_changed"); /// Notification sent when the list of available prompts changes pub type PromptListChangedNotification = NotificationNoParam; const_string!(ToolListChangedNotificationMethod = "notifications/tools/list_changed"); /// Notification sent when the list of available tools changes pub type ToolListChangedNotification = NotificationNoParam; // ============================================================================= // LOGGING // ============================================================================= /// Logging levels supported by the MCP protocol #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)] #[serde(rename_all = "lowercase")] //match spec #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")] #[deprecated( since = "2.0.0", note = "Logging is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub enum LoggingLevel { Debug, Info, Notice, Warning, Error, Critical, Alert, Emergency, } const_string!(SetLevelRequestMethod = "logging/setLevel"); /// Parameters for setting the logging level #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Logging is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct SetLevelRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, /// The desired logging level pub level: LoggingLevel, } impl SetLevelRequestParams { /// Create a new SetLevelRequestParams with the given logging level. pub fn new(level: LoggingLevel) -> Self { Self { meta: None, level } } } impl RequestParamsMeta for SetLevelRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } /// Deprecated: Use [`SetLevelRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use SetLevelRequestParams instead")] pub type SetLevelRequestParam = SetLevelRequestParams; /// Request to set the logging level #[deprecated( since = "2.0.0", note = "Logging is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub type SetLevelRequest = Request; const_string!(LoggingMessageNotificationMethod = "notifications/message"); /// Parameters for a logging message notification #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Logging is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct LoggingMessageNotificationParam { /// The severity level of this log message pub level: LoggingLevel, /// Optional logger name that generated this message #[serde(skip_serializing_if = "Option::is_none")] pub logger: Option, /// The actual log data pub data: Value, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl LoggingMessageNotificationParam { /// Create a new LoggingMessageNotificationParam. pub fn new(level: LoggingLevel, data: Value) -> Self { Self { level, logger: None, data, meta: None, } } /// Set the logger name. pub fn with_logger(mut self, logger: impl Into) -> Self { self.logger = Some(logger.into()); self } } /// Notification containing a log message #[deprecated( since = "2.0.0", note = "Logging is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub type LoggingMessageNotification = Notification; // ============================================================================= // SAMPLING (LLM INTERACTION) // ============================================================================= const_string!(CreateMessageRequestMethod = "sampling/createMessage"); #[deprecated( since = "2.0.0", note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub type CreateMessageRequest = Request; /// Represents the role of a participant in a conversation or message exchange. /// /// Used in sampling and chat contexts to distinguish between different /// types of message senders in the conversation flow. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")] pub enum Role { /// A human user or client making a request User, /// An AI assistant or server providing a response Assistant, } /// Tool selection mode (SEP-1577). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "lowercase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub enum ToolChoiceMode { /// Model decides whether to use tools #[default] Auto, /// Model must use at least one tool Required, /// Model must not use tools None, } /// Tool choice configuration (SEP-1577). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct ToolChoice { #[serde(skip_serializing_if = "Option::is_none")] pub mode: Option, } impl ToolChoice { pub fn auto() -> Self { Self { mode: Some(ToolChoiceMode::Auto), } } pub fn required() -> Self { Self { mode: Some(ToolChoiceMode::Required), } } pub fn none() -> Self { Self { mode: Some(ToolChoiceMode::None), } } } /// Single or array content wrapper (SEP-1577). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(untagged)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")] pub enum SamplingContent { Single(T), Multiple(Vec), } impl SamplingContent { /// Convert to a Vec regardless of whether it's single or multiple pub fn into_vec(self) -> Vec { match self { SamplingContent::Single(item) => vec![item], SamplingContent::Multiple(items) => items, } } /// Check if the content is empty pub fn is_empty(&self) -> bool { match self { SamplingContent::Single(_) => false, SamplingContent::Multiple(items) => items.is_empty(), } } /// Get the number of content items pub fn len(&self) -> usize { match self { SamplingContent::Single(_) => 1, SamplingContent::Multiple(items) => items.len(), } } } impl Default for SamplingContent { fn default() -> Self { SamplingContent::Multiple(Vec::new()) } } impl SamplingContent { /// Get the first item if present pub fn first(&self) -> Option<&T> { match self { SamplingContent::Single(item) => Some(item), SamplingContent::Multiple(items) => items.first(), } } /// Iterate over all content items pub fn iter(&self) -> impl Iterator { let items: Vec<&T> = match self { SamplingContent::Single(item) => vec![item], SamplingContent::Multiple(items) => items.iter().collect(), }; items.into_iter() } } impl SamplingMessageContentBlock { /// Get the text content if this is a Text variant pub fn as_text(&self) -> Option<&TextContent> { match self { SamplingMessageContentBlock::Text(text) => Some(text), _ => None, } } /// Get the tool use content if this is a ToolUse variant pub fn as_tool_use(&self) -> Option<&ToolUseContent> { match self { SamplingMessageContentBlock::ToolUse(tool_use) => Some(tool_use), _ => None, } } /// Get the tool result content if this is a ToolResult variant pub fn as_tool_result(&self) -> Option<&ToolResultContent> { match self { SamplingMessageContentBlock::ToolResult(tool_result) => Some(tool_result), _ => None, } } } impl From for SamplingContent { fn from(item: T) -> Self { SamplingContent::Single(item) } } impl From> for SamplingContent { fn from(items: Vec) -> Self { SamplingContent::Multiple(items) } } /// A message in a sampling conversation, containing a role and content. /// /// This represents a single message in a conversation flow, used primarily /// in LLM sampling requests where the conversation history is important /// for generating appropriate responses. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct SamplingMessage { /// The role of the message sender (User or Assistant) pub role: Role, /// The actual content of the message (text, image, audio, tool use, or tool result) pub content: SamplingContent, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } /// Content types for sampling messages (SEP-1577). #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", rename_all = "snake_case")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub enum SamplingMessageContentBlock { Text(TextContent), Image(ImageContent), Audio(AudioContent), /// Assistant only ToolUse(ToolUseContent), /// User only ToolResult(ToolResultContent), } #[deprecated(since = "2.0.0", note = "Renamed to SamplingMessageContentBlock")] pub type SamplingMessageContent = SamplingMessageContentBlock; impl SamplingMessageContentBlock { /// Create a text content pub fn text(text: impl Into) -> Self { Self::Text(TextContent::new(text)) } pub fn tool_use(id: impl Into, name: impl Into, input: JsonObject) -> Self { Self::ToolUse(ToolUseContent::new(id, name, input)) } pub fn tool_result(tool_use_id: impl Into, content: Vec) -> Self { Self::ToolResult(ToolResultContent::new(tool_use_id, content)) } } impl SamplingMessage { pub fn new(role: Role, content: impl Into) -> Self { Self { role, content: SamplingContent::Single(content.into()), meta: None, } } pub fn new_multiple(role: Role, contents: Vec) -> Self { Self { role, content: SamplingContent::Multiple(contents), meta: None, } } pub fn user_text(text: impl Into) -> Self { Self::new(Role::User, SamplingMessageContentBlock::text(text)) } pub fn assistant_text(text: impl Into) -> Self { Self::new(Role::Assistant, SamplingMessageContentBlock::text(text)) } pub fn user_tool_result(tool_use_id: impl Into, content: Vec) -> Self { Self::new( Role::User, SamplingMessageContentBlock::tool_result(tool_use_id, content), ) } pub fn assistant_tool_use( id: impl Into, name: impl Into, input: JsonObject, ) -> Self { Self::new( Role::Assistant, SamplingMessageContentBlock::tool_use(id, name, input), ) } } impl From for SamplingMessageContentBlock { fn from(text: TextContent) -> Self { SamplingMessageContentBlock::Text(text) } } // Conversion from String to SamplingMessageContentBlock (as text) impl From for SamplingMessageContentBlock { fn from(text: String) -> Self { SamplingMessageContentBlock::text(text) } } impl From<&str> for SamplingMessageContentBlock { fn from(text: &str) -> Self { SamplingMessageContentBlock::text(text) } } impl TryFrom for SamplingMessageContentBlock { type Error = &'static str; fn try_from(content: ContentBlock) -> Result { match content { ContentBlock::Text(text) => Ok(SamplingMessageContentBlock::Text(text)), ContentBlock::Image(image) => Ok(SamplingMessageContentBlock::Image(image)), ContentBlock::Audio(audio) => Ok(SamplingMessageContentBlock::Audio(audio)), ContentBlock::Resource(_) => { Err("Resource content is not supported in sampling messages") } ContentBlock::ResourceLink(_) => { Err("ResourceLink content is not supported in sampling messages") } } } } impl TryFrom for SamplingContent { type Error = &'static str; fn try_from(content: ContentBlock) -> Result { Ok(SamplingContent::Single(content.try_into()?)) } } /// Specifies how much context should be included in sampling requests. /// /// This allows clients to control what additional context information /// should be provided to the LLM when processing sampling requests. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub enum ContextInclusion { /// Include context from all connected MCP servers #[serde(rename = "allServers")] AllServers, /// Include no additional context #[serde(rename = "none")] None, /// Include context only from the requesting server #[serde(rename = "thisServer")] ThisServer, } /// Parameters for creating a message through LLM sampling. /// /// This structure contains all the necessary information for a client to /// generate an LLM response, including conversation history, model preferences, /// and generation parameters. /// /// This implements `TaskAugmentedRequestParamsMeta` as sampling requests can be /// long-running and may benefit from task-based execution. #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct CreateMessageRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, /// Task metadata for async task management (SEP-1319) #[serde(skip_serializing_if = "Option::is_none")] pub task: Option, /// The conversation history and current messages pub messages: Vec, /// Preferences for model selection and behavior #[serde(skip_serializing_if = "Option::is_none")] pub model_preferences: Option, /// System prompt to guide the model's behavior #[serde(skip_serializing_if = "Option::is_none")] pub system_prompt: Option, /// How much context to include from MCP servers #[serde(skip_serializing_if = "Option::is_none")] pub include_context: Option, /// Temperature for controlling randomness (0.0 to 1.0) #[serde(skip_serializing_if = "Option::is_none")] pub temperature: Option, /// Maximum number of tokens to generate pub max_tokens: u32, /// Sequences that should stop generation #[serde(skip_serializing_if = "Option::is_none")] pub stop_sequences: Option>, /// Additional metadata for the request #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, /// Tools available for the model to call (SEP-1577) #[serde(skip_serializing_if = "Option::is_none")] pub tools: Option>, /// Tool selection behavior (SEP-1577) #[serde(skip_serializing_if = "Option::is_none")] pub tool_choice: Option, } impl RequestParamsMeta for CreateMessageRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } impl TaskAugmentedRequestParamsMeta for CreateMessageRequestParams { fn task(&self) -> Option<&TaskMetadata> { self.task.as_ref() } fn task_mut(&mut self) -> &mut Option { &mut self.task } } impl CreateMessageRequestParams { /// Create a new CreateMessageRequestParams with required fields. pub fn new(messages: Vec, max_tokens: u32) -> Self { Self { meta: None, task: None, messages, model_preferences: None, system_prompt: None, include_context: None, temperature: None, max_tokens, stop_sequences: None, metadata: None, tools: None, tool_choice: None, } } /// Set model preferences. pub fn with_model_preferences(mut self, model_preferences: ModelPreferences) -> Self { self.model_preferences = Some(model_preferences); self } /// Set system prompt. pub fn with_system_prompt(mut self, system_prompt: impl Into) -> Self { self.system_prompt = Some(system_prompt.into()); self } /// Set include context. pub fn with_include_context(mut self, include_context: ContextInclusion) -> Self { self.include_context = Some(include_context); self } /// Set temperature. pub fn with_temperature(mut self, temperature: f32) -> Self { self.temperature = Some(temperature); self } /// Set stop sequences. pub fn with_stop_sequences(mut self, stop_sequences: Vec) -> Self { self.stop_sequences = Some(stop_sequences); self } /// Set metadata. pub fn with_metadata(mut self, metadata: Value) -> Self { self.metadata = Some(metadata); self } /// Set tools. pub fn with_tools(mut self, tools: Vec) -> Self { self.tools = Some(tools); self } /// Set tool choice. pub fn with_tool_choice(mut self, tool_choice: ToolChoice) -> Self { self.tool_choice = Some(tool_choice); self } /// Validate the sampling request parameters per SEP-1577 spec requirements. /// /// Checks: /// - ToolUse content is only allowed in assistant messages /// - ToolResult content is only allowed in user messages /// - Messages with tool result content MUST NOT contain other content types /// - Every assistant ToolUse must be balanced with a corresponding user ToolResult pub fn validate(&self) -> Result<(), String> { for msg in &self.messages { for content in msg.content.iter() { // ToolUse only in assistant messages, ToolResult only in user messages match content { SamplingMessageContentBlock::ToolUse(_) if msg.role != Role::Assistant => { return Err("ToolUse content is only allowed in assistant messages".into()); } SamplingMessageContentBlock::ToolResult(_) if msg.role != Role::User => { return Err("ToolResult content is only allowed in user messages".into()); } _ => {} } } // Tool result messages MUST NOT contain other content types let contents: Vec<_> = msg.content.iter().collect(); let has_tool_result = contents .iter() .any(|c| matches!(c, SamplingMessageContentBlock::ToolResult(_))); if has_tool_result && contents .iter() .any(|c| !matches!(c, SamplingMessageContentBlock::ToolResult(_))) { return Err( "SamplingMessage with tool result content MUST NOT contain other content types" .into(), ); } } // Every assistant ToolUse must be balanced with a user ToolResult self.validate_tool_use_result_balance()?; Ok(()) } fn validate_tool_use_result_balance(&self) -> Result<(), String> { let mut pending_tool_use_ids: Vec = Vec::new(); for msg in &self.messages { if msg.role == Role::Assistant { for content in msg.content.iter() { if let SamplingMessageContentBlock::ToolUse(tu) = content { pending_tool_use_ids.push(tu.id.clone()); } } } else if msg.role == Role::User { for content in msg.content.iter() { if let SamplingMessageContentBlock::ToolResult(tr) = content { if !pending_tool_use_ids.contains(&tr.tool_use_id) { return Err(format!( "ToolResult with toolUseId '{}' has no matching ToolUse", tr.tool_use_id )); } pending_tool_use_ids.retain(|id| id != &tr.tool_use_id); } } } } if !pending_tool_use_ids.is_empty() { return Err(format!( "ToolUse with id(s) {:?} not balanced with ToolResult", pending_tool_use_ids )); } Ok(()) } } /// Deprecated: Use [`CreateMessageRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use CreateMessageRequestParams instead")] pub type CreateMessageRequestParam = CreateMessageRequestParams; /// Preferences for model selection and behavior in sampling requests. /// /// This allows servers to express their preferences for which model to use /// and how to balance different priorities when the client has multiple /// model options available. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct ModelPreferences { /// Specific model names or families to prefer (e.g., "claude", "gpt") #[serde(skip_serializing_if = "Option::is_none")] pub hints: Option>, /// Priority for cost optimization (0.0 to 1.0, higher = prefer cheaper models) #[serde(skip_serializing_if = "Option::is_none")] pub cost_priority: Option, /// Priority for speed/latency (0.0 to 1.0, higher = prefer faster models) #[serde(skip_serializing_if = "Option::is_none")] pub speed_priority: Option, /// Priority for intelligence/capability (0.0 to 1.0, higher = prefer more capable models) #[serde(skip_serializing_if = "Option::is_none")] pub intelligence_priority: Option, } impl ModelPreferences { /// Create a new default ModelPreferences. pub fn new() -> Self { Self { hints: None, cost_priority: None, speed_priority: None, intelligence_priority: None, } } /// Set hints for model selection. pub fn with_hints(mut self, hints: Vec) -> Self { self.hints = Some(hints); self } /// Set cost priority (0.0 to 1.0). pub fn with_cost_priority(mut self, cost_priority: f32) -> Self { self.cost_priority = Some(cost_priority); self } /// Set speed priority (0.0 to 1.0). pub fn with_speed_priority(mut self, speed_priority: f32) -> Self { self.speed_priority = Some(speed_priority); self } /// Set intelligence priority (0.0 to 1.0). pub fn with_intelligence_priority(mut self, intelligence_priority: f32) -> Self { self.intelligence_priority = Some(intelligence_priority); self } } impl Default for ModelPreferences { fn default() -> Self { Self::new() } } /// A hint suggesting a preferred model name or family. /// /// Model hints are advisory suggestions that help clients choose appropriate /// models. They can be specific model names or general families like "claude" or "gpt". #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct ModelHint { /// The suggested model name or family identifier #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, } impl ModelHint { /// Create a new ModelHint with a name. pub fn new(name: impl Into) -> Self { Self { name: Some(name.into()), } } } // ============================================================================= // COMPLETION AND AUTOCOMPLETE // ============================================================================= /// Context for completion requests providing previously resolved arguments. /// /// This enables context-aware completion where subsequent argument completions /// can take into account the values of previously resolved arguments. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct CompletionContext { /// Previously resolved argument values that can inform completion suggestions #[serde(skip_serializing_if = "Option::is_none")] pub arguments: Option>, } impl CompletionContext { /// Create a new empty completion context pub fn new() -> Self { Self::default() } /// Create a completion context with the given arguments pub fn with_arguments(arguments: std::collections::HashMap) -> Self { Self { arguments: Some(arguments), } } /// Get a specific argument value by name pub fn get_argument(&self, name: &str) -> Option<&String> { self.arguments.as_ref()?.get(name) } /// Check if the context has any arguments pub fn has_arguments(&self) -> bool { self.arguments.as_ref().is_some_and(|args| !args.is_empty()) } /// Get all argument names pub fn argument_names(&self) -> impl Iterator { self.arguments .as_ref() .into_iter() .flat_map(|args| args.keys()) .map(|k| k.as_str()) } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct CompleteRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, pub r#ref: Reference, pub argument: ArgumentInfo, /// Optional context containing previously resolved argument values #[serde(skip_serializing_if = "Option::is_none")] pub context: Option, } impl CompleteRequestParams { /// Create a new CompleteRequestParams with required fields. pub fn new(r#ref: Reference, argument: ArgumentInfo) -> Self { Self { meta: None, r#ref, argument, context: None, } } /// Set the completion context pub fn with_context(mut self, context: CompletionContext) -> Self { self.context = Some(context); self } } impl RequestParamsMeta for CompleteRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } /// Deprecated: Use [`CompleteRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use CompleteRequestParams instead")] pub type CompleteRequestParam = CompleteRequestParams; pub type CompleteRequest = Request; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct CompletionInfo { pub values: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub total: Option, #[serde(skip_serializing_if = "Option::is_none")] pub has_more: Option, } impl CompletionInfo { /// Maximum number of completion values allowed per response according to MCP specification pub const MAX_VALUES: usize = 100; /// Create a new CompletionInfo with validation for maximum values pub fn new(values: Vec) -> Result { if values.len() > Self::MAX_VALUES { return Err(format!( "Too many completion values: {} (max: {})", values.len(), Self::MAX_VALUES )); } Ok(Self { values, total: None, has_more: None, }) } /// Create CompletionInfo with all values and no pagination pub fn with_all_values(values: Vec) -> Result { let completion = Self::new(values)?; Ok(Self { total: Some(completion.values.len() as u32), has_more: Some(false), ..completion }) } /// Create CompletionInfo with pagination information pub fn with_pagination( values: Vec, total: Option, has_more: bool, ) -> Result { let completion = Self::new(values)?; Ok(Self { total, has_more: Some(has_more), ..completion }) } /// Check if this completion response indicates more results are available pub fn has_more_results(&self) -> bool { self.has_more.unwrap_or(false) } /// Get the total number of available completions, if known pub fn total_available(&self) -> Option { self.total } /// Validate that the completion info complies with MCP specification pub fn validate(&self) -> Result<(), String> { if self.values.len() > Self::MAX_VALUES { return Err(format!( "Too many completion values: {} (max: {})", self.values.len(), Self::MAX_VALUES )); } Ok(()) } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct CompleteResult { pub completion: CompletionInfo, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl CompleteResult { /// Create a new CompleteResult with the given completion info. pub fn new(completion: CompletionInfo) -> Self { Self { completion, meta: None, } } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(tag = "type")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub enum Reference { #[serde(rename = "ref/resource")] Resource(ResourceTemplateReference), #[serde(rename = "ref/prompt")] Prompt(PromptReference), } impl Reference { /// Create a prompt reference pub fn for_prompt(name: impl Into) -> Self { // Not accepting `title` currently as it'll break the API // Until further decision, keep it `None`, modify later // if required, add `title` to the API Self::Prompt(PromptReference { name: name.into(), title: None, }) } /// Create a resource reference pub fn for_resource(uri: impl Into) -> Self { Self::Resource(ResourceTemplateReference { uri: uri.into() }) } /// Get the reference type as a string pub fn reference_type(&self) -> &'static str { match self { Self::Prompt(_) => "ref/prompt", Self::Resource(_) => "ref/resource", } } /// Extract prompt name if this is a prompt reference pub fn as_prompt_name(&self) -> Option<&str> { match self { Self::Prompt(prompt_ref) => Some(&prompt_ref.name), _ => None, } } /// Extract resource URI if this is a resource reference pub fn as_resource_uri(&self) -> Option<&str> { match self { Self::Resource(resource_ref) => Some(&resource_ref.uri), _ => None, } } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct ResourceTemplateReference { pub uri: String, } impl ResourceTemplateReference { pub fn new(uri: impl Into) -> Self { Self { uri: uri.into() } } } #[deprecated(since = "2.0.0", note = "Renamed to ResourceTemplateReference")] pub type ResourceReference = ResourceTemplateReference; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct PromptReference { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub title: Option, } impl PromptReference { /// Creates a new `PromptReference` with the given name. `title` defaults to `None`. pub fn new(name: impl Into) -> Self { Self { name: name.into(), title: None, } } /// Sets the human-readable title for this prompt reference. pub fn with_title(mut self, title: impl Into) -> Self { self.title = Some(title.into()); self } } const_string!(CompleteRequestMethod = "completion/complete"); #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct ArgumentInfo { pub name: String, pub value: String, } impl ArgumentInfo { pub fn new(name: impl Into, value: impl Into) -> Self { Self { name: name.into(), value: value.into(), } } } // ============================================================================= // ROOTS AND WORKSPACE MANAGEMENT // ============================================================================= #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Roots is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct Root { pub uri: String, #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl Root { /// Creates a new `Root` with the given URI. `name` defaults to `None`. pub fn new(uri: impl Into) -> Self { Self { uri: uri.into(), name: None, meta: None, } } /// Sets the human-readable name for this root. pub fn with_name(mut self, name: impl Into) -> Self { self.name = Some(name.into()); self } /// Sets the protocol-level metadata for this root. pub fn with_meta(mut self, meta: Meta) -> Self { self.meta = Some(meta); self } } const_string!(ListRootsRequestMethod = "roots/list"); #[deprecated( since = "2.0.0", note = "Roots is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub type ListRootsRequest = RequestNoParam; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Roots is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct ListRootsResult { pub roots: Vec, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl ListRootsResult { /// Creates a new `ListRootsResult` with the given roots. pub fn new(roots: Vec) -> Self { Self { roots, meta: None } } /// Sets the protocol-level metadata for this result. pub fn with_meta(mut self, meta: Meta) -> Self { self.meta = Some(meta); self } } const_string!(RootsListChangedNotificationMethod = "notifications/roots/list_changed"); pub type RootsListChangedNotification = NotificationNoParam; // ============================================================================= // ELICITATION (INTERACTIVE USER INPUT) // ============================================================================= // Method constants for elicitation operations. // Elicitation allows servers to request interactive input from users during tool execution. const_string!(ElicitationCreateRequestMethod = "elicitation/create"); const_string!(ElicitationResponseNotificationMethod = "notifications/elicitation/response"); const_string!(ElicitationCompletionNotificationMethod = "notifications/elicitation/complete"); /// Represents the possible actions a user can take in response to an elicitation request. /// /// When a server requests user input through elicitation, the user can: /// - Accept: Provide the requested information and continue /// - Decline: Refuse to provide the information but continue the operation /// - Cancel: Stop the entire operation #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(rename_all = "lowercase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub enum ElicitationAction { /// User accepts the request and provides the requested information Accept, /// User declines to provide the information but allows the operation to continue Decline, /// User cancels the entire operation Cancel, } /// Helper enum for deserializing CreateElicitationRequestParam with backward compatibility. /// When mode is missing, it defaults to FormElicitationParam. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(tag = "mode")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] enum CreateElicitationRequestParamDeserializeHelper { #[serde(rename = "form", rename_all = "camelCase")] FormElicitationParam { #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] meta: Option, message: String, requested_schema: ElicitationSchema, }, #[serde(rename = "url", rename_all = "camelCase")] UrlElicitationParam { #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] meta: Option, message: String, url: String, elicitation_id: String, }, #[serde(untagged, rename_all = "camelCase")] FormElicitationParamBackwardsCompat { #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] meta: Option, message: String, requested_schema: ElicitationSchema, }, } impl TryFrom for ElicitRequestParams { type Error = serde_json::Error; fn try_from( value: CreateElicitationRequestParamDeserializeHelper, ) -> Result { match value { CreateElicitationRequestParamDeserializeHelper::FormElicitationParam { meta, message, requested_schema, } | CreateElicitationRequestParamDeserializeHelper::FormElicitationParamBackwardsCompat { meta, message, requested_schema, } => Ok(ElicitRequestParams::FormElicitationParams { meta, message, requested_schema, }), CreateElicitationRequestParamDeserializeHelper::UrlElicitationParam { meta, message, url, elicitation_id, } => Ok(ElicitRequestParams::UrlElicitationParams { meta, message, url, elicitation_id, }), } } } /// Parameters for creating an elicitation request to gather user input. /// /// This structure contains everything needed to request interactive input from a user: /// - A human-readable message explaining what information is needed /// - A type-safe schema defining the expected structure of the response /// /// # Example /// 1. Form-based elicitation request /// ```rust /// use rmcp::model::*; /// /// let params = ElicitRequestParams::FormElicitationParams { /// meta: None, /// message: "Please provide your email".to_string(), /// requested_schema: ElicitationSchema::builder() /// .required_email("email") /// .build() /// .unwrap(), /// }; /// ``` /// 2. URL-based elicitation request /// ```rust /// use rmcp::model::*; /// let params = ElicitRequestParams::UrlElicitationParams { /// meta: None, /// message: "Please provide your feedback at the following URL".to_string(), /// url: "https://example.com/feedback".to_string(), /// elicitation_id: "unique-id-123".to_string(), /// }; /// ``` #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde( tag = "mode", try_from = "CreateElicitationRequestParamDeserializeHelper" )] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub enum ElicitRequestParams { #[serde(rename = "form", rename_all = "camelCase")] FormElicitationParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] meta: Option, /// Human-readable message explaining what input is needed from the user. /// This should be clear and provide sufficient context for the user to understand /// what information they need to provide. message: String, /// Type-safe schema defining the expected structure and validation rules for the user's response. /// This enforces the MCP 2025-06-18 specification that elicitation schemas must be objects /// with primitive-typed properties. requested_schema: ElicitationSchema, }, #[serde(rename = "url", rename_all = "camelCase")] UrlElicitationParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] meta: Option, /// Human-readable message explaining what input is needed from the user. /// This should be clear and provide sufficient context for the user to understand /// what information they need to provide. message: String, /// The URL where the user can provide the requested information. /// The client should direct the user to this URL to complete the elicitation. url: String, /// The unique identifier for this elicitation request. elicitation_id: String, }, } impl RequestParamsMeta for ElicitRequestParams { fn meta(&self) -> Option<&Meta> { match self { ElicitRequestParams::FormElicitationParams { meta, .. } => meta.as_ref(), ElicitRequestParams::UrlElicitationParams { meta, .. } => meta.as_ref(), } } fn meta_mut(&mut self) -> &mut Option { match self { ElicitRequestParams::FormElicitationParams { meta, .. } => meta, ElicitRequestParams::UrlElicitationParams { meta, .. } => meta, } } } /// Deprecated: Use [`ElicitRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use ElicitRequestParams instead")] pub type CreateElicitationRequestParam = ElicitRequestParams; #[deprecated(since = "2.0.0", note = "Renamed to ElicitRequestParams")] pub type CreateElicitationRequestParams = ElicitRequestParams; /// The result returned by a client in response to an elicitation request. /// /// Contains the user's decision (accept/decline/cancel) and optionally their input data /// if they chose to accept the request. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct ElicitResult { /// The user's decision on how to handle the elicitation request pub action: ElicitationAction, /// The actual data provided by the user, if they accepted the request. /// Must conform to the JSON schema specified in the original request. /// Only present when action is Accept. #[serde(skip_serializing_if = "Option::is_none")] pub content: Option, /// Optional protocol-level metadata for this result. #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl ElicitResult { /// Create a new ElicitResult. pub fn new(action: ElicitationAction) -> Self { Self { action, content: None, meta: None, } } /// Set the content on this result. pub fn with_content(mut self, content: Value) -> Self { self.content = Some(content); self } /// Set the metadata on this result. pub fn with_meta(mut self, meta: Meta) -> Self { self.meta = Some(meta); self } } #[deprecated(since = "2.0.0", note = "Renamed to ElicitResult")] pub type CreateElicitationResult = ElicitResult; /// Request type for creating an elicitation to gather user input pub type ElicitRequest = Request; #[deprecated(since = "2.0.0", note = "Renamed to ElicitRequest")] pub type CreateElicitationRequest = ElicitRequest; /// Notification parameters for an url elicitation completion notification. #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct ElicitationResponseNotificationParam { pub elicitation_id: String, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl ElicitationResponseNotificationParam { /// Create a new ElicitationResponseNotificationParam. pub fn new(elicitation_id: impl Into) -> Self { Self { elicitation_id: elicitation_id.into(), meta: None, } } } /// Notification sent when an url elicitation process is completed. pub type ElicitationCompleteNotification = Notification; #[deprecated(since = "2.0.0", note = "Renamed to ElicitationCompleteNotification")] pub type ElicitationCompletionNotification = ElicitationCompleteNotification; // ============================================================================= // TOOL EXECUTION RESULTS // ============================================================================= /// The result of a tool call operation. /// /// Contains the content returned by the tool execution and an optional /// flag indicating whether the operation resulted in an error. #[derive(Default, Debug, Serialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct CallToolResult { /// The content returned by the tool (text, images, etc.) #[serde(default)] pub content: Vec, /// An optional JSON object that represents the structured result of the tool call #[serde(skip_serializing_if = "Option::is_none")] pub structured_content: Option, /// Whether this result represents an error condition #[serde(skip_serializing_if = "Option::is_none")] pub is_error: Option, /// Optional protocol-level metadata for this result #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } // Custom Deserialize implementation that: // 1. Defaults `content` to `[]` when the field is missing (lenient per Postel's law) // 2. Requires at least one known field to be present, so that `CallToolResult` doesn't // greedily match arbitrary JSON objects when used inside `#[serde(untagged)]` enums // (e.g. `ServerResult`), which would shadow `CustomResult`. impl<'de> Deserialize<'de> for CallToolResult { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { #[derive(Deserialize)] #[serde(rename_all = "camelCase")] struct Helper { content: Option>, structured_content: Option, is_error: Option, #[serde(rename = "_meta")] meta: Option, } let helper = Helper::deserialize(deserializer)?; if helper.content.is_none() && helper.structured_content.is_none() && helper.is_error.is_none() && helper.meta.is_none() { return Err(serde::de::Error::custom( "expected at least one known CallToolResult field \ (content, structuredContent, isError, or _meta)", )); } Ok(CallToolResult { content: helper.content.unwrap_or_default(), structured_content: helper.structured_content, is_error: helper.is_error, meta: helper.meta, }) } } impl CallToolResult { /// Create a successful tool result with unstructured content pub fn success(content: Vec) -> Self { CallToolResult { content, structured_content: None, is_error: Some(false), meta: None, } } /// Create a tool-level error result with caller-visible content. /// /// # When to use this vs `Err(ErrorData)` /// /// MCP distinguishes two failure modes for a `call_tool` invocation, and /// the right one to use depends on **whose problem it is**: /// /// - **Tool-level error** — `Ok(CallToolResult::error(...))`. /// The request was valid and routed to your tool, but executing the /// tool failed in a way the caller should see (a query returned no /// rows, an external API returned 500, the user's input is plausible /// but produced no result, etc.). The caller's MCP client renders the /// `content` you provide; your message reaches the user. **This is the /// right choice for almost every "the tool ran and didn't work" case.** /// /// - **Protocol error** — `Err(ErrorData)` with a JSON-RPC code. /// The server cannot route the request at all, or an infrastructure /// error makes the server itself unusable /// ([`ErrorCode::INTERNAL_ERROR`], `-32603`). MCP clients typically /// render protocol errors opaquely (e.g. "Tool result missing due to /// internal error") — the caller does **not** see your message. /// /// # Example /// /// ```rust,ignore /// use rmcp::model::{CallToolResult, Content, ErrorData}; /// /// async fn lookup(query: &str) -> Result { /// // Caller passed a malformed query — the server can't run anything. /// // This is a protocol error, the caller's client will render it /// // as -32602 invalid_params: /// if query.is_empty() { /// return Err(ErrorData::invalid_params("query must be non-empty", None)); /// } /// /// // Tool ran, no result. Caller should see the explanation: /// let rows = run_query(query).await; /// if rows.is_empty() { /// return Ok(CallToolResult::error(vec![ContentBlock::text( /// format!("no rows matched '{query}'"), /// )])); /// } /// /// Ok(CallToolResult::success(vec![ContentBlock::text(format_rows(&rows))])) /// } /// # async fn run_query(_: &str) -> Vec<&'static str> { vec![] } /// # fn format_rows(_: &[&str]) -> String { String::new() } /// ``` pub fn error(content: Vec) -> Self { CallToolResult { content, structured_content: None, is_error: Some(true), meta: None, } } /// Create a successful tool result with structured content /// /// # Example /// /// ```rust,ignore /// use rmcp::model::CallToolResult; /// use serde_json::json; /// /// let result = CallToolResult::structured(json!({ /// "temperature": 22.5, /// "humidity": 65, /// "description": "Partly cloudy" /// })); /// ``` pub fn structured(value: Value) -> Self { CallToolResult { content: vec![ContentBlock::text(value.to_string())], structured_content: Some(value), is_error: Some(false), meta: None, } } /// Create an error tool result with structured content /// /// # Example /// /// ```rust,ignore /// use rmcp::model::CallToolResult; /// use serde_json::json; /// /// let result = CallToolResult::structured_error(json!({ /// "error_code": "INVALID_INPUT", /// "message": "Temperature value out of range", /// "details": { /// "min": -50, /// "max": 50, /// "provided": 100 /// } /// })); /// ``` pub fn structured_error(value: Value) -> Self { CallToolResult { content: vec![ContentBlock::text(value.to_string())], structured_content: Some(value), is_error: Some(true), meta: None, } } /// Set the metadata on this result pub fn with_meta(mut self, meta: Option) -> Self { self.meta = meta; self } /// Convert the `structured_content` part of response into a certain type. /// /// # About json schema validation /// Since rust is a strong type language, we don't need to do json schema validation here. /// /// But if you do have to validate the response data, you can use [`jsonschema`](https://crates.io/crates/jsonschema) crate. pub fn into_typed(self) -> Result where T: DeserializeOwned, { let raw_text = match (self.structured_content, &self.content.first()) { (Some(value), _) => return serde_json::from_value(value), (None, Some(contents)) => { if let Some(text) = contents.as_text() { let text = &text.text; Some(text) } else { None } } (None, None) => None, }; if let Some(text) = raw_text { return serde_json::from_str(text); } serde_json::from_value(serde_json::Value::Null) } } const_string!(ListToolsRequestMethod = "tools/list"); /// Request to list all available tools from a server pub type ListToolsRequest = RequestOptionalParam; paginated_result!( ListToolsResult { tools: Vec } ); const_string!(CallToolRequestMethod = "tools/call"); /// Parameters for calling a tool provided by an MCP server. /// /// Contains the tool name and optional arguments needed to execute /// the tool operation. /// /// This implements `TaskAugmentedRequestParamsMeta` as tool calls can be /// long-running and may benefit from task-based execution. #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct CallToolRequestParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, /// The name of the tool to call pub name: Cow<'static, str>, /// Arguments to pass to the tool (must match the tool's input schema) #[serde(skip_serializing_if = "Option::is_none")] pub arguments: Option, /// Task metadata for async task management (SEP-1319) #[serde(skip_serializing_if = "Option::is_none")] pub task: Option, } impl CallToolRequestParams { /// Creates a new `CallToolRequestParams` with the given tool name. pub fn new(name: impl Into>) -> Self { Self { meta: None, name: name.into(), arguments: None, task: None, } } /// Sets the arguments for this tool call. pub fn with_arguments(mut self, arguments: JsonObject) -> Self { self.arguments = Some(arguments); self } /// Sets the task metadata for this tool call. pub fn with_task(mut self, task: TaskMetadata) -> Self { self.task = Some(task); self } } impl RequestParamsMeta for CallToolRequestParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } impl TaskAugmentedRequestParamsMeta for CallToolRequestParams { fn task(&self) -> Option<&TaskMetadata> { self.task.as_ref() } fn task_mut(&mut self) -> &mut Option { &mut self.task } } /// Deprecated: Use [`CallToolRequestParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use CallToolRequestParams instead")] pub type CallToolRequestParam = CallToolRequestParams; /// Request to call a specific tool pub type CallToolRequest = Request; /// Result of sampling/createMessage (SEP-1577). /// The result of a sampling/createMessage request containing the generated response. /// /// This structure contains the generated message along with metadata about /// how the generation was performed and why it stopped. #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] #[deprecated( since = "2.0.0", note = "Sampling is deprecated by SEP-2577 and will be removed in a future release. See https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577" )] pub struct CreateMessageResult { /// The identifier of the model that generated the response pub model: String, /// The reason why generation stopped (e.g., "endTurn", "maxTokens") #[serde(skip_serializing_if = "Option::is_none")] pub stop_reason: Option, /// The generated message with role and content #[serde(flatten)] pub message: SamplingMessage, } impl CreateMessageResult { /// Create a new CreateMessageResult with required fields. pub fn new(message: SamplingMessage, model: String) -> Self { Self { message, model, stop_reason: None, } } pub const STOP_REASON_END_TURN: &str = "endTurn"; pub const STOP_REASON_END_SEQUENCE: &str = "stopSequence"; pub const STOP_REASON_END_MAX_TOKEN: &str = "maxTokens"; pub const STOP_REASON_TOOL_USE: &str = "toolUse"; /// Set the stop reason. pub fn with_stop_reason(mut self, stop_reason: impl Into) -> Self { self.stop_reason = Some(stop_reason.into()); self } /// Set the model identifier. pub fn with_model(mut self, model: impl Into) -> Self { self.model = model.into(); self } /// Validate the result per SEP-1577: role must be "assistant". pub fn validate(&self) -> Result<(), String> { if self.message.role != Role::Assistant { return Err("CreateMessageResult role must be 'assistant'".into()); } Ok(()) } } #[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct GetPromptResult { #[serde(skip_serializing_if = "Option::is_none")] pub description: Option, pub messages: Vec, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl GetPromptResult { /// Create a new GetPromptResult with required fields. pub fn new(messages: Vec) -> Self { Self { description: None, messages, meta: None, } } /// Set the description pub fn with_description>(mut self, description: D) -> Self { self.description = Some(description.into()); self } } // ============================================================================= // TASK MANAGEMENT // ============================================================================= const_string!(GetTaskMethod = "tasks/get"); pub type GetTaskRequest = Request; #[deprecated(since = "2.0.0", note = "Renamed to GetTaskMethod")] pub type GetTaskInfoMethod = GetTaskMethod; #[deprecated(since = "2.0.0", note = "Renamed to GetTaskRequest")] pub type GetTaskInfoRequest = GetTaskRequest; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct GetTaskParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, pub task_id: String, } impl GetTaskParams { pub fn new(task_id: impl Into) -> Self { Self { meta: None, task_id: task_id.into(), } } } impl RequestParamsMeta for GetTaskParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } #[deprecated(since = "2.0.0", note = "Renamed to GetTaskParams")] pub type GetTaskInfoParams = GetTaskParams; #[deprecated(since = "0.13.0", note = "Use GetTaskParams instead")] pub type GetTaskInfoParam = GetTaskParams; const_string!(ListTasksMethod = "tasks/list"); pub type ListTasksRequest = RequestOptionalParam; const_string!(GetTaskPayloadMethod = "tasks/result"); pub type GetTaskPayloadRequest = Request; #[deprecated(since = "2.0.0", note = "Renamed to GetTaskPayloadMethod")] pub type GetTaskResultMethod = GetTaskPayloadMethod; #[deprecated(since = "2.0.0", note = "Renamed to GetTaskPayloadRequest")] pub type GetTaskResultRequest = GetTaskPayloadRequest; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct GetTaskPayloadParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, pub task_id: String, } impl GetTaskPayloadParams { pub fn new(task_id: impl Into) -> Self { Self { meta: None, task_id: task_id.into(), } } } impl RequestParamsMeta for GetTaskPayloadParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } #[deprecated(since = "2.0.0", note = "Renamed to GetTaskPayloadParams")] pub type GetTaskResultParams = GetTaskPayloadParams; #[deprecated(since = "2.0.0", note = "Renamed to GetTaskPayloadParams")] pub type GetTaskResultParam = GetTaskPayloadParams; const_string!(CancelTaskMethod = "tasks/cancel"); pub type CancelTaskRequest = Request; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct CancelTaskParams { /// Protocol-level metadata for this request (SEP-1319) #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, pub task_id: String, } impl CancelTaskParams { pub fn new(task_id: impl Into) -> Self { Self { meta: None, task_id: task_id.into(), } } } impl RequestParamsMeta for CancelTaskParams { fn meta(&self) -> Option<&Meta> { self.meta.as_ref() } fn meta_mut(&mut self) -> &mut Option { &mut self.meta } } /// Deprecated: Use [`CancelTaskParams`] instead (SEP-1319 compliance). #[deprecated(since = "0.13.0", note = "Use CancelTaskParams instead")] pub type CancelTaskParam = CancelTaskParams; // --------------------------------------------------------------------------- // Task status notification (spec `notifications/tasks/status`) // --------------------------------------------------------------------------- const_string!(TaskStatusNotificationMethod = "notifications/tasks/status"); /// Parameters for a task status notification (spec `TaskStatusNotificationParams`). /// /// The task fields are flattened at the top level: `NotificationParams & Task`. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct TaskStatusNotificationParam { #[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")] pub meta: Option, #[serde(flatten)] pub task: crate::model::Task, } impl TaskStatusNotificationParam { pub fn new(task: crate::model::Task) -> Self { Self { meta: None, task } } pub fn with_meta(mut self, meta: Meta) -> Self { self.meta = Some(meta); self } } impl From for TaskStatusNotificationParam { fn from(task: crate::model::Task) -> Self { Self::new(task) } } impl Deref for TaskStatusNotificationParam { type Target = crate::model::Task; fn deref(&self) -> &Self::Target { &self.task } } impl DerefMut for TaskStatusNotificationParam { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.task } } pub type TaskStatusNotification = Notification; /// Deprecated: Use [`GetTaskResult`] instead (spec alignment). #[deprecated(since = "0.15.0", note = "Use GetTaskResult instead")] pub type GetTaskInfoResult = GetTaskResult; #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)] #[serde(rename_all = "camelCase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[non_exhaustive] pub struct ListTasksResult { pub tasks: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub next_cursor: Option, #[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] pub meta: Option, } impl ListTasksResult { pub fn new(tasks: Vec) -> Self { Self { tasks, next_cursor: None, meta: None, } } } // ============================================================================= // MESSAGE TYPE UNIONS // ============================================================================= macro_rules! ts_union { ( export type $U:ident = $($rest:tt)* ) => { ts_union!(@declare $U { $($rest)* }); ts_union!(@impl_from $U { $($rest)* }); }; (@declare $U:ident { $($variant:tt)* }) => { ts_union!(@declare_variant $U { } {$($variant)*} ); }; (@declare_variant $U:ident { $($declared:tt)* } {$(|)? box $V:ident $($rest:tt)*}) => { ts_union!(@declare_variant $U { $($declared)* $V(Box<$V>), } {$($rest)*}); }; (@declare_variant $U:ident { $($declared:tt)* } {$(|)? $V:ident $($rest:tt)*}) => { ts_union!(@declare_variant $U { $($declared)* $V($V), } {$($rest)*}); }; (@declare_variant $U:ident { $($declared:tt)* } { ; }) => { ts_union!(@declare_end $U { $($declared)* } ); }; (@declare_end $U:ident { $($declared:tt)* }) => { #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] #[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum $U { $($declared)* } }; (@impl_from $U: ident {$(|)? box $V:ident $($rest:tt)*}) => { impl From<$V> for $U { fn from(value: $V) -> Self { $U::$V(Box::new(value)) } } ts_union!(@impl_from $U {$($rest)*}); }; (@impl_from $U: ident {$(|)? $V:ident $($rest:tt)*}) => { impl From<$V> for $U { fn from(value: $V) -> Self { $U::$V(value) } } ts_union!(@impl_from $U {$($rest)*}); }; (@impl_from $U: ident { ; }) => {}; (@impl_from $U: ident { }) => {}; } ts_union!( export type ClientRequest = | PingRequest | InitializeRequest | CompleteRequest | SetLevelRequest | GetPromptRequest | ListPromptsRequest | ListResourcesRequest | ListResourceTemplatesRequest | ReadResourceRequest | SubscribeRequest | UnsubscribeRequest | CallToolRequest | ListToolsRequest | GetTaskRequest | ListTasksRequest | GetTaskPayloadRequest | CancelTaskRequest | CustomRequest; ); impl ClientRequest { pub fn method(&self) -> &str { match &self { ClientRequest::PingRequest(r) => r.method.as_str(), ClientRequest::InitializeRequest(r) => r.method.as_str(), ClientRequest::CompleteRequest(r) => r.method.as_str(), ClientRequest::SetLevelRequest(r) => r.method.as_str(), ClientRequest::GetPromptRequest(r) => r.method.as_str(), ClientRequest::ListPromptsRequest(r) => r.method.as_str(), ClientRequest::ListResourcesRequest(r) => r.method.as_str(), ClientRequest::ListResourceTemplatesRequest(r) => r.method.as_str(), ClientRequest::ReadResourceRequest(r) => r.method.as_str(), ClientRequest::SubscribeRequest(r) => r.method.as_str(), ClientRequest::UnsubscribeRequest(r) => r.method.as_str(), ClientRequest::CallToolRequest(r) => r.method.as_str(), ClientRequest::ListToolsRequest(r) => r.method.as_str(), ClientRequest::GetTaskRequest(r) => r.method.as_str(), ClientRequest::ListTasksRequest(r) => r.method.as_str(), ClientRequest::GetTaskPayloadRequest(r) => r.method.as_str(), ClientRequest::CancelTaskRequest(r) => r.method.as_str(), ClientRequest::CustomRequest(r) => r.method.as_str(), } } } ts_union!( export type ClientNotification = | CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification | TaskStatusNotification | CustomNotification; ); ts_union!( export type ClientResult = box CreateMessageResult | ListRootsResult | ElicitResult | EmptyResult | CustomResult; ); impl ClientResult { pub fn empty(_: ()) -> ClientResult { ClientResult::EmptyResult(EmptyResult {}) } } pub type ClientJsonRpcMessage = JsonRpcMessage; ts_union!( export type ServerRequest = | PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest | CustomRequest; ); ts_union!( export type ServerNotification = | CancelledNotification | ProgressNotification | LoggingMessageNotification | ResourceUpdatedNotification | ResourceListChangedNotification | ToolListChangedNotification | PromptListChangedNotification | ElicitationCompleteNotification | TaskStatusNotification | CustomNotification; ); ts_union!( export type ServerResult = | InitializeResult | CompleteResult | GetPromptResult | ListPromptsResult | ListResourcesResult | ListResourceTemplatesResult | ReadResourceResult | ListToolsResult | ElicitResult | CreateTaskResult | ListTasksResult | GetTaskResult | CancelTaskResult | CallToolResult | GetTaskPayloadResult | EmptyResult | CustomResult ; ); impl ServerResult { pub fn empty(_: ()) -> ServerResult { ServerResult::EmptyResult(EmptyResult {}) } } pub type ServerJsonRpcMessage = JsonRpcMessage; impl TryInto for ServerNotification { type Error = ServerNotification; fn try_into(self) -> Result { if let ServerNotification::CancelledNotification(t) = self { Ok(t) } else { Err(self) } } } impl TryInto for ClientNotification { type Error = ClientNotification; fn try_into(self) -> Result { if let ClientNotification::CancelledNotification(t) = self { Ok(t) } else { Err(self) } } } // ============================================================================= // TESTS // ============================================================================= #[cfg(test)] mod tests { use serde_json::json; use super::*; #[test] #[allow(deprecated)] fn deprecated_aliases_still_resolve() { // 하위호환: 구 이름이 새 타입으로 여전히 resolve되는지 확인. let _: CreateElicitationResult = ElicitResult::new(ElicitationAction::Accept); let _: GetTaskResultParams = GetTaskPayloadParams::new("task-1"); let _: ResourceReference = ResourceTemplateReference::new("res://x"); } #[test] fn cancelled_notification_request_id_is_optional_on_wire() { // None → requestId 생략 let p = CancelledNotificationParam::new(None, Some("user cancelled".into())); let v = serde_json::to_value(&p).unwrap(); assert!(v.get("requestId").is_none()); // Some → requestId 방출 + 라운드트립 let p = CancelledNotificationParam::new(Some(RequestId::Number(1)), None); let v = serde_json::to_value(&p).unwrap(); assert_eq!(v["requestId"], json!(1)); let back: CancelledNotificationParam = serde_json::from_value(v).unwrap(); assert_eq!(back.request_id, Some(RequestId::Number(1))); } #[test] fn test_notification_serde() { let raw = json!( { "jsonrpc": JsonRpcVersion2_0, "method": InitializedNotificationMethod, }); let message: ClientJsonRpcMessage = serde_json::from_value(raw.clone()).expect("invalid notification"); match &message { ClientJsonRpcMessage::Notification(JsonRpcNotification { notification: ClientNotification::InitializedNotification(_n), .. }) => {} _ => panic!("Expected Notification"), } let json = serde_json::to_value(message).expect("valid json"); assert_eq!(json, raw); } #[test] fn test_custom_client_notification_roundtrip() { let raw = json!( { "jsonrpc": JsonRpcVersion2_0, "method": "notifications/custom", "params": {"foo": "bar"}, }); let message: ClientJsonRpcMessage = serde_json::from_value(raw.clone()).expect("invalid notification"); match &message { ClientJsonRpcMessage::Notification(JsonRpcNotification { notification: ClientNotification::CustomNotification(notification), .. }) => { assert_eq!(notification.method, "notifications/custom"); assert_eq!( notification .params .as_ref() .and_then(|p| p.get("foo")) .expect("foo present"), "bar" ); } _ => panic!("Expected custom client notification"), } let json = serde_json::to_value(message).expect("valid json"); assert_eq!(json, raw); } #[test] fn test_custom_server_notification_roundtrip() { let raw = json!( { "jsonrpc": JsonRpcVersion2_0, "method": "notifications/custom-server", "params": {"hello": "world"}, }); let message: ServerJsonRpcMessage = serde_json::from_value(raw.clone()).expect("invalid notification"); match &message { ServerJsonRpcMessage::Notification(JsonRpcNotification { notification: ServerNotification::CustomNotification(notification), .. }) => { assert_eq!(notification.method, "notifications/custom-server"); assert_eq!( notification .params .as_ref() .and_then(|p| p.get("hello")) .expect("hello present"), "world" ); } _ => panic!("Expected custom server notification"), } let json = serde_json::to_value(message).expect("valid json"); assert_eq!(json, raw); } #[test] fn test_custom_request_roundtrip() { let raw = json!( { "jsonrpc": JsonRpcVersion2_0, "id": 42, "method": "requests/custom", "params": {"foo": "bar"}, }); let message: ClientJsonRpcMessage = serde_json::from_value(raw.clone()).expect("invalid request"); match &message { ClientJsonRpcMessage::Request(JsonRpcRequest { id, request, .. }) => { assert_eq!(id, &RequestId::Number(42)); match request { ClientRequest::CustomRequest(custom) => { let expected_request = json!({ "method": "requests/custom", "params": {"foo": "bar"}, }); let actual_request = serde_json::to_value(custom).expect("serialize custom request"); assert_eq!(actual_request, expected_request); } other => panic!("Expected custom request, got: {other:?}"), } } other => panic!("Expected request, got: {other:?}"), } let json = serde_json::to_value(message).expect("valid json"); assert_eq!(json, raw); } #[test] fn test_request_conversion() { let raw = json!( { "jsonrpc": JsonRpcVersion2_0, "id": 1, "method": "request", "params": {"key": "value"}, }); let message: JsonRpcMessage = serde_json::from_value(raw.clone()).expect("invalid request"); match &message { JsonRpcMessage::Request(r) => { assert_eq!(r.id, RequestId::Number(1)); assert_eq!(r.request.method, "request"); assert_eq!( &r.request.params, json!({"key": "value"}) .as_object() .expect("should be an object") ); } _ => panic!("Expected Request"), } let json = serde_json::to_value(&message).expect("valid json"); assert_eq!(json, raw); } #[test] fn test_initial_request_response_serde() { let request = json!({ "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": { "roots": { "listChanged": true }, "sampling": {} }, "clientInfo": { "name": "ExampleClient", "version": "1.0.0" } } }); let raw_response_json = json!({ "jsonrpc": "2.0", "id": 1, "result": { "protocolVersion": "2024-11-05", "capabilities": { "logging": {}, "prompts": { "listChanged": true }, "resources": { "subscribe": true, "listChanged": true }, "tools": { "listChanged": true } }, "serverInfo": { "name": "ExampleServer", "version": "1.0.0" } } }); let request: ClientJsonRpcMessage = serde_json::from_value(request.clone()).expect("invalid request"); let (request, id) = request.into_request().expect("should be a request"); assert_eq!(id, RequestId::Number(1)); #[allow(deprecated)] match request { ClientRequest::InitializeRequest(Request { method: _, params: InitializeRequestParam { meta: _, protocol_version: _, capabilities, client_info, }, .. }) => { assert_eq!(capabilities.roots.unwrap().list_changed, Some(true)); let sampling = capabilities.sampling.unwrap(); assert_eq!(sampling.tools, None); assert_eq!(sampling.context, None); assert_eq!(client_info.name, "ExampleClient"); assert_eq!(client_info.version, "1.0.0"); } _ => panic!("Expected InitializeRequest"), } let server_response: ServerJsonRpcMessage = serde_json::from_value(raw_response_json.clone()).expect("invalid response"); let (response, id) = server_response .clone() .into_response() .expect("expect response"); assert_eq!(id, RequestId::Number(1)); match response { ServerResult::InitializeResult(InitializeResult { protocol_version: _, capabilities, server_info, instructions, .. }) => { assert_eq!(capabilities.logging.unwrap().len(), 0); assert_eq!(capabilities.prompts.unwrap().list_changed, Some(true)); assert_eq!( capabilities.resources.as_ref().unwrap().subscribe, Some(true) ); assert_eq!(capabilities.resources.unwrap().list_changed, Some(true)); assert_eq!(capabilities.tools.unwrap().list_changed, Some(true)); assert_eq!(server_info.name, "ExampleServer"); assert_eq!(server_info.version, "1.0.0"); assert_eq!(server_info.icons, None); assert_eq!(instructions, None); } other => panic!("Expected InitializeResult, got {other:?}"), } let server_response_json: Value = serde_json::to_value(&server_response).expect("msg"); assert_eq!(server_response_json, raw_response_json); } #[test] fn test_negative_and_large_request_ids() { // Test negative ID let negative_id_json = json!({ "jsonrpc": "2.0", "id": -1, "method": "test", "params": {} }); let message: JsonRpcMessage = serde_json::from_value(negative_id_json.clone()).expect("Should parse negative ID"); match &message { JsonRpcMessage::Request(r) => { assert_eq!(r.id, RequestId::Number(-1)); } _ => panic!("Expected Request"), } // Test roundtrip serialization let serialized = serde_json::to_value(&message).expect("Should serialize"); assert_eq!(serialized, negative_id_json); // Test large negative ID let large_negative_json = json!({ "jsonrpc": "2.0", "id": -9007199254740991i64, // JavaScript's MIN_SAFE_INTEGER "method": "test", "params": {} }); let message: JsonRpcMessage = serde_json::from_value(large_negative_json.clone()) .expect("Should parse large negative ID"); match &message { JsonRpcMessage::Request(r) => { assert_eq!(r.id, RequestId::Number(-9007199254740991i64)); } _ => panic!("Expected Request"), } // Test large positive ID (JavaScript's MAX_SAFE_INTEGER) let large_positive_json = json!({ "jsonrpc": "2.0", "id": 9007199254740991i64, "method": "test", "params": {} }); let message: JsonRpcMessage = serde_json::from_value(large_positive_json.clone()) .expect("Should parse large positive ID"); match &message { JsonRpcMessage::Request(r) => { assert_eq!(r.id, RequestId::Number(9007199254740991i64)); } _ => panic!("Expected Request"), } // Test zero ID let zero_id_json = json!({ "jsonrpc": "2.0", "id": 0, "method": "test", "params": {} }); let message: JsonRpcMessage = serde_json::from_value(zero_id_json.clone()).expect("Should parse zero ID"); match &message { JsonRpcMessage::Request(r) => { assert_eq!(r.id, RequestId::Number(0)); } _ => panic!("Expected Request"), } } #[test] fn test_protocol_version_order() { let v1 = ProtocolVersion::V_2024_11_05; let v2 = ProtocolVersion::V_2025_03_26; let v3 = ProtocolVersion::V_2025_06_18; let v4 = ProtocolVersion::V_2025_11_25; assert!(v1 < v2); assert!(v2 < v3); assert!(v3 < v4); } #[test] fn test_icon_serialization() { let icon = Icon { src: "https://example.com/icon.png".to_string(), mime_type: Some("image/png".to_string()), sizes: Some(vec!["48x48".to_string()]), theme: Some(IconTheme::Light), }; let json = serde_json::to_value(&icon).unwrap(); assert_eq!(json["src"], "https://example.com/icon.png"); assert_eq!(json["mimeType"], "image/png"); assert_eq!(json["sizes"][0], "48x48"); assert_eq!(json["theme"], "light"); // Test deserialization let deserialized: Icon = serde_json::from_value(json).unwrap(); assert_eq!(deserialized, icon); } #[test] fn test_icon_minimal() { let icon = Icon { src: "data:image/svg+xml;base64,PHN2Zy8+".to_string(), mime_type: None, sizes: None, theme: None, }; let json = serde_json::to_value(&icon).unwrap(); assert_eq!(json["src"], "data:image/svg+xml;base64,PHN2Zy8+"); assert!(json.get("mimeType").is_none()); assert!(json.get("sizes").is_none()); assert!(json.get("theme").is_none()); } #[test] fn test_implementation_with_icons() { let implementation = Implementation { name: "test-server".to_string(), title: Some("Test Server".to_string()), version: "1.0.0".to_string(), description: Some("A test server for unit testing".to_string()), icons: Some(vec![ Icon { src: "https://example.com/icon.png".to_string(), mime_type: Some("image/png".to_string()), sizes: Some(vec!["48x48".to_string()]), theme: Some(IconTheme::Dark), }, Icon { src: "https://example.com/icon.svg".to_string(), mime_type: Some("image/svg+xml".to_string()), sizes: Some(vec!["any".to_string()]), theme: Some(IconTheme::Light), }, ]), website_url: Some("https://example.com".to_string()), }; let json = serde_json::to_value(&implementation).unwrap(); assert_eq!(json["name"], "test-server"); assert_eq!(json["description"], "A test server for unit testing"); assert_eq!(json["websiteUrl"], "https://example.com"); assert!(json["icons"].is_array()); assert_eq!(json["icons"][0]["src"], "https://example.com/icon.png"); assert_eq!(json["icons"][0]["sizes"][0], "48x48"); assert_eq!(json["icons"][1]["mimeType"], "image/svg+xml"); assert_eq!(json["icons"][1]["sizes"][0], "any"); assert_eq!(json["icons"][0]["theme"], "dark"); assert_eq!(json["icons"][1]["theme"], "light"); } #[test] fn test_backward_compatibility() { // Test that old JSON without icons still deserializes correctly let old_json = json!({ "name": "legacy-server", "version": "0.9.0" }); let implementation: Implementation = serde_json::from_value(old_json).unwrap(); assert_eq!(implementation.name, "legacy-server"); assert_eq!(implementation.version, "0.9.0"); assert_eq!(implementation.description, None); assert_eq!(implementation.icons, None); assert_eq!(implementation.website_url, None); } #[test] fn test_initialize_with_icons() { let init_result = InitializeResult { protocol_version: ProtocolVersion::default(), capabilities: ServerCapabilities::default(), server_info: Implementation { name: "icon-server".to_string(), title: None, version: "2.0.0".to_string(), description: None, icons: Some(vec![Icon { src: "https://example.com/server.png".to_string(), mime_type: Some("image/png".to_string()), sizes: Some(vec!["48x48".to_string()]), theme: Some(IconTheme::Light), }]), website_url: Some("https://docs.example.com".to_string()), }, instructions: None, meta: None, }; let json = serde_json::to_value(&init_result).unwrap(); assert!(json["serverInfo"]["icons"].is_array()); assert_eq!( json["serverInfo"]["icons"][0]["src"], "https://example.com/server.png" ); assert_eq!(json["serverInfo"]["icons"][0]["sizes"][0], "48x48"); assert_eq!(json["serverInfo"]["icons"][0]["theme"], "light"); assert_eq!(json["serverInfo"]["websiteUrl"], "https://docs.example.com"); } #[test] fn test_elicitation_deserialization_untagged() { // Test deserialization without the "type" field (should default to FormElicitationParam) let json_data_without_tag = json!({ "message": "Please provide more details.", "requestedSchema": { "title": "User Details", "type": "object", "properties": { "name": { "type": "string" }, "age": { "type": "integer" } }, "required": ["name", "age"] } }); let elicitation: ElicitRequestParams = serde_json::from_value(json_data_without_tag).expect("Deserialization failed"); if let ElicitRequestParams::FormElicitationParams { meta, message, requested_schema, } = elicitation { assert_eq!(meta, None); assert_eq!(message, "Please provide more details."); assert_eq!(requested_schema.title, Some(Cow::from("User Details"))); assert_eq!(requested_schema.type_, ObjectTypeConst); } else { panic!("Expected FormElicitationParam"); } } #[test] fn test_elicitation_deserialization() { let json_data_form = json!({ "_meta": { "meta_form_key_1": "meta form value 1" }, "mode": "form", "message": "Please provide more details.", "requestedSchema": { "title": "User Details", "type": "object", "properties": { "name": { "type": "string" }, "age": { "type": "integer" } }, "required": ["name", "age"] } }); let elicitation_form: ElicitRequestParams = serde_json::from_value(json_data_form).expect("Deserialization failed"); if let ElicitRequestParams::FormElicitationParams { meta, message, requested_schema, } = elicitation_form { assert_eq!( meta, Some(Meta(object!({ "meta_form_key_1": "meta form value 1" }))) ); assert_eq!(message, "Please provide more details."); assert_eq!(requested_schema.title, Some(Cow::from("User Details"))); assert_eq!(requested_schema.type_, ObjectTypeConst); } else { panic!("Expected FormElicitationParam"); } let json_data_url = json!({ "_meta": { "meta_url_key_1": "meta url value 1" }, "mode": "url", "message": "Please fill out the form at the following URL.", "url": "https://example.com/form", "elicitationId": "elicitation-123" }); let elicitation_url: ElicitRequestParams = serde_json::from_value(json_data_url).expect("Deserialization failed"); if let ElicitRequestParams::UrlElicitationParams { meta, message, url, elicitation_id, } = elicitation_url { assert_eq!( meta, Some(Meta(object!({ "meta_url_key_1": "meta url value 1" }))) ); assert_eq!(message, "Please fill out the form at the following URL."); assert_eq!(url, "https://example.com/form"); assert_eq!(elicitation_id, "elicitation-123"); } else { panic!("Expected UrlElicitationParam"); } } #[test] fn test_elicitation_serialization() { let form_elicitation = ElicitRequestParams::FormElicitationParams { meta: Some(Meta(object!({ "meta_form_key_1": "meta form value 1" }))), message: "Please provide more details.".to_string(), requested_schema: ElicitationSchema::builder() .title("User Details") .string_property("name", |s| s) .build() .expect("Valid schema"), }; let json_form = serde_json::to_value(&form_elicitation).expect("Serialization failed"); let expected_form_json = json!({ "_meta": { "meta_form_key_1": "meta form value 1" }, "mode": "form", "message": "Please provide more details.", "requestedSchema": { "title":"User Details", "type":"object", "properties":{ "name": { "type": "string" }, }, } }); assert_eq!(json_form, expected_form_json); let url_elicitation = ElicitRequestParams::UrlElicitationParams { meta: Some(Meta(object!({ "meta_url_key_1": "meta url value 1" }))), message: "Please fill out the form at the following URL.".to_string(), url: "https://example.com/form".to_string(), elicitation_id: "elicitation-123".to_string(), }; let json_url = serde_json::to_value(&url_elicitation).expect("Serialization failed"); let expected_url_json = json!({ "_meta": { "meta_url_key_1": "meta url value 1" }, "mode": "url", "message": "Please fill out the form at the following URL.", "url": "https://example.com/form", "elicitationId": "elicitation-123" }); assert_eq!(json_url, expected_url_json); } #[test] fn notification_without_params_should_deserialize_as_bare_jsonrpc_message() { let payload = b"{\"method\":\"notifications/initialized\",\"jsonrpc\":\"2.0\"}"; let result: Result = serde_json::from_slice(payload); assert!( matches!(result, Ok(JsonRpcMessage::Notification(_))), "Expected Ok(Notification), got: {:?}", result ); } }