use std::{borrow::Cow, 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::*;
pub type JsonObject<F = Value> = serde_json::Map<String, F>;
pub fn object(value: serde_json::Value) -> JsonObject {
debug_assert!(value.is_object());
match value {
serde_json::Value::Object(map) => map,
_ => JsonObject::default(),
}
}
#[cfg(feature = "macros")]
#[macro_export]
macro_rules! object {
({$($tt:tt)*}) => {
$crate::model::object(serde_json::json! {
{$($tt)*}
})
};
}
#[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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
$value.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for $name {
fn deserialize<D>(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");
#[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_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;
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,
];
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Serialize for ProtocolVersion {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for ProtocolVersion {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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),
_ => {}
}
Ok(ProtocolVersion(Cow::Owned(s)))
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum NumberOrString {
Number(i64),
String(Arc<str>),
}
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<Self, D::Error>
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() {
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)
}
}
pub type RequestId = NumberOrString;
#[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);
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Request<M = String, P = JsonObject> {
pub method: M,
pub params: P,
#[cfg_attr(feature = "schemars", schemars(skip))]
pub extensions: Extensions,
}
impl<M: Default, P> Request<M, P> {
pub fn new(params: P) -> Self {
Self {
method: Default::default(),
params,
extensions: Extensions::default(),
}
}
}
impl<M, P> GetExtensions for Request<M, P> {
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<M = String, P = JsonObject> {
pub method: M,
pub params: Option<P>,
#[cfg_attr(feature = "schemars", schemars(skip))]
pub extensions: Extensions,
}
impl<M: Default, P> RequestOptionalParam<M, P> {
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<M = String> {
pub method: M,
#[cfg_attr(feature = "schemars", schemars(skip))]
pub extensions: Extensions,
}
impl<M> GetExtensions for RequestNoParam<M> {
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<M = String, P = JsonObject> {
pub method: M,
pub params: P,
#[cfg_attr(feature = "schemars", schemars(skip))]
pub extensions: Extensions,
}
impl<M: Default, P> Notification<M, P> {
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<M = String> {
pub method: M,
#[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<R = Request> {
pub jsonrpc: JsonRpcVersion2_0,
pub id: RequestId,
#[serde(flatten)]
pub request: R,
}
impl<R> JsonRpcRequest<R> {
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<R = JsonObject> {
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,
pub id: RequestId,
pub error: ErrorData,
}
impl JsonRpcError {
pub fn new(id: RequestId, 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<N = Notification> {
pub jsonrpc: JsonRpcVersion2_0,
#[serde(flatten)]
pub notification: N,
}
#[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);
}
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ErrorData {
pub code: ErrorCode,
pub message: Cow<'static, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
impl ErrorData {
pub fn new(
code: ErrorCode,
message: impl Into<Cow<'static, str>>,
data: Option<Value>,
) -> Self {
Self {
code,
message: message.into(),
data,
}
}
pub fn resource_not_found(message: impl Into<Cow<'static, str>>, data: Option<Value>) -> Self {
Self::new(ErrorCode::RESOURCE_NOT_FOUND, message, data)
}
pub fn parse_error(message: impl Into<Cow<'static, str>>, data: Option<Value>) -> Self {
Self::new(ErrorCode::PARSE_ERROR, message, data)
}
pub fn invalid_request(message: impl Into<Cow<'static, str>>, data: Option<Value>) -> Self {
Self::new(ErrorCode::INVALID_REQUEST, message, data)
}
pub fn method_not_found<M: ConstString>() -> Self {
Self::new(ErrorCode::METHOD_NOT_FOUND, M::VALUE, None)
}
pub fn invalid_params(message: impl Into<Cow<'static, str>>, data: Option<Value>) -> Self {
Self::new(ErrorCode::INVALID_PARAMS, message, data)
}
pub fn internal_error(message: impl Into<Cow<'static, str>>, data: Option<Value>) -> Self {
Self::new(ErrorCode::INTERNAL_ERROR, message, data)
}
pub fn url_elicitation_required(
message: impl Into<Cow<'static, str>>,
data: Option<Value>,
) -> Self {
Self::new(ErrorCode::URL_ELICITATION_REQUIRED, message, data)
}
}
#[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<Req = Request, Resp = DefaultResponse, Noti = Notification> {
Request(JsonRpcRequest<Req>),
Response(JsonRpcResponse<Resp>),
Notification(JsonRpcNotification<Noti>),
Error(JsonRpcError),
}
impl<Req, Resp, Not> JsonRpcMessage<Req, Resp, Not> {
#[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: RequestId) -> 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<Not> {
match self {
JsonRpcMessage::Notification(n) => Some(n.notification),
_ => None,
}
}
pub fn into_error(self) -> Option<(ErrorData, RequestId)> {
match self {
JsonRpcMessage::Error(e) => Some((e.error, e.id)),
_ => None,
}
}
pub fn into_result(self) -> Option<(Result<Resp, ErrorData>, RequestId)> {
match self {
JsonRpcMessage::Response(r) => Some((Ok(r.result), r.id)),
JsonRpcMessage::Error(e) => Some((Err(e.error), e.id)),
_ => None,
}
}
}
pub type EmptyResult = EmptyObject;
impl From<()> for EmptyResult {
fn from(_value: ()) -> Self {
EmptyResult {}
}
}
impl From<EmptyResult> for () {
fn from(_value: EmptyResult) {}
}
#[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)
}
pub fn result_as<T: DeserializeOwned>(&self) -> Result<T, serde_json::Error> {
serde_json::from_value(self.0.clone())
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct CancelledNotificationParam {
pub request_id: RequestId,
pub reason: Option<String>,
}
const_string!(CancelledNotificationMethod = "notifications/cancelled");
pub type CancelledNotification =
Notification<CancelledNotificationMethod, CancelledNotificationParam>;
#[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<Value>,
#[cfg_attr(feature = "schemars", schemars(skip))]
pub extensions: Extensions,
}
impl CustomNotification {
pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
Self {
method: method.into(),
params,
extensions: Extensions::default(),
}
}
pub fn params_as<T: DeserializeOwned>(&self) -> Result<Option<T>, serde_json::Error> {
self.params
.as_ref()
.map(|params| serde_json::from_value(params.clone()))
.transpose()
}
}
#[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<Value>,
#[cfg_attr(feature = "schemars", schemars(skip))]
pub extensions: Extensions,
}
impl CustomRequest {
pub fn new(method: impl Into<String>, params: Option<Value>) -> Self {
Self {
method: method.into(),
params,
extensions: Extensions::default(),
}
}
pub fn params_as<T: DeserializeOwned>(&self) -> Result<Option<T>, serde_json::Error> {
self.params
.as_ref()
.map(|params| serde_json::from_value(params.clone()))
.transpose()
}
}
const_string!(InitializeResultMethod = "initialize");
pub type InitializeRequest = Request<InitializeResultMethod, InitializeRequestParams>;
const_string!(InitializedNotificationMethod = "notifications/initialized");
pub type InitializedNotification = NotificationNoParam<InitializedNotificationMethod>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct InitializeRequestParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub protocol_version: ProtocolVersion,
pub capabilities: ClientCapabilities,
pub client_info: Implementation,
}
impl 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<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use InitializeRequestParams instead")]
pub type InitializeRequestParam = InitializeRequestParams;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct InitializeResult {
pub protocol_version: ProtocolVersion,
pub capabilities: ServerCapabilities,
pub server_info: Implementation,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
}
impl InitializeResult {
pub fn new(capabilities: ServerCapabilities) -> Self {
Self {
protocol_version: ProtocolVersion::default(),
capabilities,
server_info: Implementation::from_build_env(),
instructions: None,
}
}
pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = Some(instructions.into());
self
}
pub fn with_server_info(mut self, server_info: Implementation) -> Self {
self.server_info = server_info;
self
}
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,
}
}
}
#[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(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash, Copy)]
#[serde(rename_all = "lowercase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub enum IconTheme {
Light,
Dark,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Icon {
pub src: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sizes: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub theme: Option<IconTheme>,
}
impl Icon {
pub fn new(src: impl Into<String>) -> Self {
Self {
src: src.into(),
mime_type: None,
sizes: None,
theme: None,
}
}
pub fn with_mime_type(mut self, mime_type: impl Into<String>) -> Self {
self.mime_type = Some(mime_type.into());
self
}
pub fn with_sizes(mut self, sizes: Vec<String>) -> Self {
self.sizes = Some(sizes);
self
}
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<String>,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub icons: Option<Vec<Icon>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub website_url: Option<String>,
}
impl Default for Implementation {
fn default() -> Self {
Self::from_build_env()
}
}
impl Implementation {
pub fn new(name: impl Into<String>, version: impl Into<String>) -> 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,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_icons(mut self, icons: Vec<Icon>) -> Self {
self.icons = Some(icons);
self
}
pub fn with_website_url(mut self, website_url: impl Into<String>) -> 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 {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cursor: Option<String>,
}
impl PaginatedRequestParams {
pub fn with_cursor(mut self, cursor: Option<String>) -> 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<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use PaginatedRequestParams instead")]
pub type PaginatedRequestParam = PaginatedRequestParams;
const_string!(PingRequestMethod = "ping");
pub type PingRequest = RequestNoParam<PingRequestMethod>;
const_string!(ProgressNotificationMethod = "notifications/progress");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ProgressNotificationParam {
pub progress_token: ProgressToken,
pub progress: f64,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
}
impl ProgressNotificationParam {
pub fn new(progress_token: ProgressToken, progress: f64) -> Self {
Self {
progress_token,
progress,
total: None,
message: None,
}
}
pub fn with_total(mut self, total: f64) -> Self {
self.total = Some(total);
self
}
pub fn with_message(mut self, message: impl Into<String>) -> Self {
self.message = Some(message.into());
self
}
}
pub type ProgressNotification = Notification<ProgressNotificationMethod, ProgressNotificationParam>;
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<Meta>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<Cursor>,
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,
}
}
}
};
}
const_string!(ListResourcesRequestMethod = "resources/list");
pub type ListResourcesRequest =
RequestOptionalParam<ListResourcesRequestMethod, PaginatedRequestParams>;
paginated_result!(ListResourcesResult {
resources: Vec<Resource>
});
const_string!(ListResourceTemplatesRequestMethod = "resources/templates/list");
pub type ListResourceTemplatesRequest =
RequestOptionalParam<ListResourceTemplatesRequestMethod, PaginatedRequestParams>;
paginated_result!(ListResourceTemplatesResult {
resource_templates: Vec<ResourceTemplate>
});
const_string!(ReadResourceRequestMethod = "resources/read");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ReadResourceRequestParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub uri: String,
}
impl ReadResourceRequestParams {
pub fn new(uri: impl Into<String>) -> Self {
Self {
meta: None,
uri: uri.into(),
}
}
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<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use ReadResourceRequestParams instead")]
pub type ReadResourceRequestParam = ReadResourceRequestParams;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ReadResourceResult {
pub contents: Vec<ResourceContents>,
}
impl ReadResourceResult {
pub fn new(contents: Vec<ResourceContents>) -> Self {
Self { contents }
}
}
pub type ReadResourceRequest = Request<ReadResourceRequestMethod, ReadResourceRequestParams>;
const_string!(ResourceListChangedNotificationMethod = "notifications/resources/list_changed");
pub type ResourceListChangedNotification =
NotificationNoParam<ResourceListChangedNotificationMethod>;
const_string!(SubscribeRequestMethod = "resources/subscribe");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct SubscribeRequestParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub uri: String,
}
impl SubscribeRequestParams {
pub fn new(uri: impl Into<String>) -> 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<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use SubscribeRequestParams instead")]
pub type SubscribeRequestParam = SubscribeRequestParams;
pub type SubscribeRequest = Request<SubscribeRequestMethod, SubscribeRequestParams>;
const_string!(UnsubscribeRequestMethod = "resources/unsubscribe");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct UnsubscribeRequestParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub uri: String,
}
impl UnsubscribeRequestParams {
pub fn new(uri: impl Into<String>) -> 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<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use UnsubscribeRequestParams instead")]
pub type UnsubscribeRequestParam = UnsubscribeRequestParams;
pub type UnsubscribeRequest = Request<UnsubscribeRequestMethod, UnsubscribeRequestParams>;
const_string!(ResourceUpdatedNotificationMethod = "notifications/resources/updated");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ResourceUpdatedNotificationParam {
pub uri: String,
}
impl ResourceUpdatedNotificationParam {
pub fn new(uri: impl Into<String>) -> Self {
Self { uri: uri.into() }
}
}
pub type ResourceUpdatedNotification =
Notification<ResourceUpdatedNotificationMethod, ResourceUpdatedNotificationParam>;
const_string!(ListPromptsRequestMethod = "prompts/list");
pub type ListPromptsRequest =
RequestOptionalParam<ListPromptsRequestMethod, PaginatedRequestParams>;
paginated_result!(ListPromptsResult {
prompts: Vec<Prompt>
});
const_string!(GetPromptRequestMethod = "prompts/get");
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct GetPromptRequestParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<JsonObject>,
}
impl GetPromptRequestParams {
pub fn new(name: impl Into<String>) -> Self {
Self {
meta: None,
name: name.into(),
arguments: None,
}
}
pub fn with_arguments(mut self, arguments: JsonObject) -> Self {
self.arguments = Some(arguments);
self
}
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<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use GetPromptRequestParams instead")]
pub type GetPromptRequestParam = GetPromptRequestParams;
pub type GetPromptRequest = Request<GetPromptRequestMethod, GetPromptRequestParams>;
const_string!(PromptListChangedNotificationMethod = "notifications/prompts/list_changed");
pub type PromptListChangedNotification = NotificationNoParam<PromptListChangedNotificationMethod>;
const_string!(ToolListChangedNotificationMethod = "notifications/tools/list_changed");
pub type ToolListChangedNotification = NotificationNoParam<ToolListChangedNotificationMethod>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
#[serde(rename_all = "lowercase")] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum LoggingLevel {
Debug,
Info,
Notice,
Warning,
Error,
Critical,
Alert,
Emergency,
}
const_string!(SetLevelRequestMethod = "logging/setLevel");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct SetLevelRequestParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub level: LoggingLevel,
}
impl SetLevelRequestParams {
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<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use SetLevelRequestParams instead")]
pub type SetLevelRequestParam = SetLevelRequestParams;
pub type SetLevelRequest = Request<SetLevelRequestMethod, SetLevelRequestParams>;
const_string!(LoggingMessageNotificationMethod = "notifications/message");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct LoggingMessageNotificationParam {
pub level: LoggingLevel,
#[serde(skip_serializing_if = "Option::is_none")]
pub logger: Option<String>,
pub data: Value,
}
impl LoggingMessageNotificationParam {
pub fn new(level: LoggingLevel, data: Value) -> Self {
Self {
level,
logger: None,
data,
}
}
pub fn with_logger(mut self, logger: impl Into<String>) -> Self {
self.logger = Some(logger.into());
self
}
}
pub type LoggingMessageNotification =
Notification<LoggingMessageNotificationMethod, LoggingMessageNotificationParam>;
const_string!(CreateMessageRequestMethod = "sampling/createMessage");
pub type CreateMessageRequest = Request<CreateMessageRequestMethod, CreateMessageRequestParams>;
#[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 {
User,
Assistant,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum ToolChoiceMode {
#[default]
Auto,
Required,
None,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ToolChoice {
#[serde(skip_serializing_if = "Option::is_none")]
pub mode: Option<ToolChoiceMode>,
}
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),
}
}
}
#[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<T> {
Single(T),
Multiple(Vec<T>),
}
impl<T> SamplingContent<T> {
pub fn into_vec(self) -> Vec<T> {
match self {
SamplingContent::Single(item) => vec![item],
SamplingContent::Multiple(items) => items,
}
}
pub fn is_empty(&self) -> bool {
match self {
SamplingContent::Single(_) => false,
SamplingContent::Multiple(items) => items.is_empty(),
}
}
pub fn len(&self) -> usize {
match self {
SamplingContent::Single(_) => 1,
SamplingContent::Multiple(items) => items.len(),
}
}
}
impl<T> Default for SamplingContent<T> {
fn default() -> Self {
SamplingContent::Multiple(Vec::new())
}
}
impl<T> SamplingContent<T> {
pub fn first(&self) -> Option<&T> {
match self {
SamplingContent::Single(item) => Some(item),
SamplingContent::Multiple(items) => items.first(),
}
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
let items: Vec<&T> = match self {
SamplingContent::Single(item) => vec![item],
SamplingContent::Multiple(items) => items.iter().collect(),
};
items.into_iter()
}
}
impl SamplingMessageContent {
pub fn as_text(&self) -> Option<&RawTextContent> {
match self {
SamplingMessageContent::Text(text) => Some(text),
_ => None,
}
}
pub fn as_tool_use(&self) -> Option<&ToolUseContent> {
match self {
SamplingMessageContent::ToolUse(tool_use) => Some(tool_use),
_ => None,
}
}
pub fn as_tool_result(&self) -> Option<&ToolResultContent> {
match self {
SamplingMessageContent::ToolResult(tool_result) => Some(tool_result),
_ => None,
}
}
}
impl<T> From<T> for SamplingContent<T> {
fn from(item: T) -> Self {
SamplingContent::Single(item)
}
}
impl<T> From<Vec<T>> for SamplingContent<T> {
fn from(items: Vec<T>) -> Self {
SamplingContent::Multiple(items)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct SamplingMessage {
pub role: Role,
pub content: SamplingContent<SamplingMessageContent>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum SamplingMessageContent {
Text(RawTextContent),
Image(RawImageContent),
Audio(RawAudioContent),
ToolUse(ToolUseContent),
ToolResult(ToolResultContent),
}
impl SamplingMessageContent {
pub fn text(text: impl Into<String>) -> Self {
Self::Text(RawTextContent {
text: text.into(),
meta: None,
})
}
pub fn tool_use(id: impl Into<String>, name: impl Into<String>, input: JsonObject) -> Self {
Self::ToolUse(ToolUseContent::new(id, name, input))
}
pub fn tool_result(tool_use_id: impl Into<String>, content: Vec<Content>) -> Self {
Self::ToolResult(ToolResultContent::new(tool_use_id, content))
}
}
impl SamplingMessage {
pub fn new(role: Role, content: impl Into<SamplingMessageContent>) -> Self {
Self {
role,
content: SamplingContent::Single(content.into()),
meta: None,
}
}
pub fn new_multiple(role: Role, contents: Vec<SamplingMessageContent>) -> Self {
Self {
role,
content: SamplingContent::Multiple(contents),
meta: None,
}
}
pub fn user_text(text: impl Into<String>) -> Self {
Self::new(Role::User, SamplingMessageContent::text(text))
}
pub fn assistant_text(text: impl Into<String>) -> Self {
Self::new(Role::Assistant, SamplingMessageContent::text(text))
}
pub fn user_tool_result(tool_use_id: impl Into<String>, content: Vec<Content>) -> Self {
Self::new(
Role::User,
SamplingMessageContent::tool_result(tool_use_id, content),
)
}
pub fn assistant_tool_use(
id: impl Into<String>,
name: impl Into<String>,
input: JsonObject,
) -> Self {
Self::new(
Role::Assistant,
SamplingMessageContent::tool_use(id, name, input),
)
}
}
impl From<RawTextContent> for SamplingMessageContent {
fn from(text: RawTextContent) -> Self {
SamplingMessageContent::Text(text)
}
}
impl From<String> for SamplingMessageContent {
fn from(text: String) -> Self {
SamplingMessageContent::text(text)
}
}
impl From<&str> for SamplingMessageContent {
fn from(text: &str) -> Self {
SamplingMessageContent::text(text)
}
}
impl TryFrom<Content> for SamplingMessageContent {
type Error = &'static str;
fn try_from(content: Content) -> Result<Self, Self::Error> {
match content.raw {
RawContent::Text(text) => Ok(SamplingMessageContent::Text(text)),
RawContent::Image(image) => Ok(SamplingMessageContent::Image(image)),
RawContent::Audio(audio) => Ok(SamplingMessageContent::Audio(audio)),
RawContent::Resource(_) => {
Err("Resource content is not supported in sampling messages")
}
RawContent::ResourceLink(_) => {
Err("ResourceLink content is not supported in sampling messages")
}
}
}
}
impl TryFrom<Content> for SamplingContent<SamplingMessageContent> {
type Error = &'static str;
fn try_from(content: Content) -> Result<Self, Self::Error> {
Ok(SamplingContent::Single(content.try_into()?))
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum ContextInclusion {
#[serde(rename = "allServers")]
AllServers,
#[serde(rename = "none")]
None,
#[serde(rename = "thisServer")]
ThisServer,
}
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CreateMessageRequestParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<JsonObject>,
pub messages: Vec<SamplingMessage>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model_preferences: Option<ModelPreferences>,
#[serde(skip_serializing_if = "Option::is_none")]
pub system_prompt: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_context: Option<ContextInclusion>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f32>,
pub max_tokens: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_sequences: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<Vec<Tool>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tool_choice: Option<ToolChoice>,
}
impl RequestParamsMeta for CreateMessageRequestParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
&mut self.meta
}
}
impl TaskAugmentedRequestParamsMeta for CreateMessageRequestParams {
fn task(&self) -> Option<&JsonObject> {
self.task.as_ref()
}
fn task_mut(&mut self) -> &mut Option<JsonObject> {
&mut self.task
}
}
impl CreateMessageRequestParams {
pub fn new(messages: Vec<SamplingMessage>, 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,
}
}
pub fn with_model_preferences(mut self, model_preferences: ModelPreferences) -> Self {
self.model_preferences = Some(model_preferences);
self
}
pub fn with_system_prompt(mut self, system_prompt: impl Into<String>) -> Self {
self.system_prompt = Some(system_prompt.into());
self
}
pub fn with_include_context(mut self, include_context: ContextInclusion) -> Self {
self.include_context = Some(include_context);
self
}
pub fn with_temperature(mut self, temperature: f32) -> Self {
self.temperature = Some(temperature);
self
}
pub fn with_stop_sequences(mut self, stop_sequences: Vec<String>) -> Self {
self.stop_sequences = Some(stop_sequences);
self
}
pub fn with_metadata(mut self, metadata: Value) -> Self {
self.metadata = Some(metadata);
self
}
pub fn with_tools(mut self, tools: Vec<Tool>) -> Self {
self.tools = Some(tools);
self
}
pub fn with_tool_choice(mut self, tool_choice: ToolChoice) -> Self {
self.tool_choice = Some(tool_choice);
self
}
pub fn validate(&self) -> Result<(), String> {
for msg in &self.messages {
for content in msg.content.iter() {
match content {
SamplingMessageContent::ToolUse(_) if msg.role != Role::Assistant => {
return Err("ToolUse content is only allowed in assistant messages".into());
}
SamplingMessageContent::ToolResult(_) if msg.role != Role::User => {
return Err("ToolResult content is only allowed in user messages".into());
}
_ => {}
}
}
let contents: Vec<_> = msg.content.iter().collect();
let has_tool_result = contents
.iter()
.any(|c| matches!(c, SamplingMessageContent::ToolResult(_)));
if has_tool_result
&& contents
.iter()
.any(|c| !matches!(c, SamplingMessageContent::ToolResult(_)))
{
return Err(
"SamplingMessage with tool result content MUST NOT contain other content types"
.into(),
);
}
}
self.validate_tool_use_result_balance()?;
Ok(())
}
fn validate_tool_use_result_balance(&self) -> Result<(), String> {
let mut pending_tool_use_ids: Vec<String> = Vec::new();
for msg in &self.messages {
if msg.role == Role::Assistant {
for content in msg.content.iter() {
if let SamplingMessageContent::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 SamplingMessageContent::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(since = "0.13.0", note = "Use CreateMessageRequestParams instead")]
pub type CreateMessageRequestParam = CreateMessageRequestParams;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ModelPreferences {
#[serde(skip_serializing_if = "Option::is_none")]
pub hints: Option<Vec<ModelHint>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cost_priority: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub speed_priority: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub intelligence_priority: Option<f32>,
}
impl ModelPreferences {
pub fn new() -> Self {
Self {
hints: None,
cost_priority: None,
speed_priority: None,
intelligence_priority: None,
}
}
pub fn with_hints(mut self, hints: Vec<ModelHint>) -> Self {
self.hints = Some(hints);
self
}
pub fn with_cost_priority(mut self, cost_priority: f32) -> Self {
self.cost_priority = Some(cost_priority);
self
}
pub fn with_speed_priority(mut self, speed_priority: f32) -> Self {
self.speed_priority = Some(speed_priority);
self
}
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()
}
}
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ModelHint {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl ModelHint {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: Some(name.into()),
}
}
}
#[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 CompletionContext {
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<std::collections::HashMap<String, String>>,
}
impl CompletionContext {
pub fn new() -> Self {
Self::default()
}
pub fn with_arguments(arguments: std::collections::HashMap<String, String>) -> Self {
Self {
arguments: Some(arguments),
}
}
pub fn get_argument(&self, name: &str) -> Option<&String> {
self.arguments.as_ref()?.get(name)
}
pub fn has_arguments(&self) -> bool {
self.arguments.as_ref().is_some_and(|args| !args.is_empty())
}
pub fn argument_names(&self) -> impl Iterator<Item = &str> {
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 {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub r#ref: Reference,
pub argument: ArgumentInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<CompletionContext>,
}
impl CompleteRequestParams {
pub fn new(r#ref: Reference, argument: ArgumentInfo) -> Self {
Self {
meta: None,
r#ref,
argument,
context: None,
}
}
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<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use CompleteRequestParams instead")]
pub type CompleteRequestParam = CompleteRequestParams;
pub type CompleteRequest = Request<CompleteRequestMethod, CompleteRequestParams>;
#[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 CompletionInfo {
pub values: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_more: Option<bool>,
}
impl CompletionInfo {
pub const MAX_VALUES: usize = 100;
pub fn new(values: Vec<String>) -> Result<Self, String> {
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,
})
}
pub fn with_all_values(values: Vec<String>) -> Result<Self, String> {
let completion = Self::new(values)?;
Ok(Self {
total: Some(completion.values.len() as u32),
has_more: Some(false),
..completion
})
}
pub fn with_pagination(
values: Vec<String>,
total: Option<u32>,
has_more: bool,
) -> Result<Self, String> {
let completion = Self::new(values)?;
Ok(Self {
total,
has_more: Some(has_more),
..completion
})
}
pub fn has_more_results(&self) -> bool {
self.has_more.unwrap_or(false)
}
pub fn total_available(&self) -> Option<u32> {
self.total
}
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,
}
impl CompleteResult {
pub fn new(completion: CompletionInfo) -> Self {
Self { completion }
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(tag = "type")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum Reference {
#[serde(rename = "ref/resource")]
Resource(ResourceReference),
#[serde(rename = "ref/prompt")]
Prompt(PromptReference),
}
impl Reference {
pub fn for_prompt(name: impl Into<String>) -> Self {
Self::Prompt(PromptReference {
name: name.into(),
title: None,
})
}
pub fn for_resource(uri: impl Into<String>) -> Self {
Self::Resource(ResourceReference { uri: uri.into() })
}
pub fn reference_type(&self) -> &'static str {
match self {
Self::Prompt(_) => "ref/prompt",
Self::Resource(_) => "ref/resource",
}
}
pub fn as_prompt_name(&self) -> Option<&str> {
match self {
Self::Prompt(prompt_ref) => Some(&prompt_ref.name),
_ => None,
}
}
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))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ResourceReference {
pub uri: String,
}
#[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<String>,
}
impl PromptReference {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
title: None,
}
}
pub fn with_title(mut self, title: impl Into<String>) -> 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))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ArgumentInfo {
pub name: String,
pub value: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct Root {
pub uri: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
impl Root {
pub fn new(uri: impl Into<String>) -> Self {
Self {
uri: uri.into(),
name: None,
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
}
const_string!(ListRootsRequestMethod = "roots/list");
pub type ListRootsRequest = RequestNoParam<ListRootsRequestMethod>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ListRootsResult {
pub roots: Vec<Root>,
}
impl ListRootsResult {
pub fn new(roots: Vec<Root>) -> Self {
Self { roots }
}
}
const_string!(RootsListChangedNotificationMethod = "notifications/roots/list_changed");
pub type RootsListChangedNotification = NotificationNoParam<RootsListChangedNotificationMethod>;
const_string!(ElicitationCreateRequestMethod = "elicitation/create");
const_string!(ElicitationResponseNotificationMethod = "notifications/elicitation/response");
const_string!(ElicitationCompletionNotificationMethod = "notifications/elicitation/complete");
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum ElicitationAction {
Accept,
Decline,
Cancel,
}
#[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<Meta>,
message: String,
requested_schema: ElicitationSchema,
},
#[serde(rename = "url", rename_all = "camelCase")]
UrlElicitationParam {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
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<Meta>,
message: String,
requested_schema: ElicitationSchema,
},
}
impl TryFrom<CreateElicitationRequestParamDeserializeHelper> for CreateElicitationRequestParams {
type Error = serde_json::Error;
fn try_from(
value: CreateElicitationRequestParamDeserializeHelper,
) -> Result<Self, Self::Error> {
match value {
CreateElicitationRequestParamDeserializeHelper::FormElicitationParam {
meta,
message,
requested_schema,
}
| CreateElicitationRequestParamDeserializeHelper::FormElicitationParamBackwardsCompat {
meta,
message,
requested_schema,
} => Ok(CreateElicitationRequestParams::FormElicitationParams {
meta,
message,
requested_schema,
}),
CreateElicitationRequestParamDeserializeHelper::UrlElicitationParam {
meta,
message,
url,
elicitation_id,
} => Ok(CreateElicitationRequestParams::UrlElicitationParams {
meta,
message,
url,
elicitation_id,
}),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(
tag = "mode",
try_from = "CreateElicitationRequestParamDeserializeHelper"
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum CreateElicitationRequestParams {
#[serde(rename = "form", rename_all = "camelCase")]
FormElicitationParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
message: String,
requested_schema: ElicitationSchema,
},
#[serde(rename = "url", rename_all = "camelCase")]
UrlElicitationParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
meta: Option<Meta>,
message: String,
url: String,
elicitation_id: String,
},
}
impl RequestParamsMeta for CreateElicitationRequestParams {
fn meta(&self) -> Option<&Meta> {
match self {
CreateElicitationRequestParams::FormElicitationParams { meta, .. } => meta.as_ref(),
CreateElicitationRequestParams::UrlElicitationParams { meta, .. } => meta.as_ref(),
}
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
match self {
CreateElicitationRequestParams::FormElicitationParams { meta, .. } => meta,
CreateElicitationRequestParams::UrlElicitationParams { meta, .. } => meta,
}
}
}
#[deprecated(since = "0.13.0", note = "Use CreateElicitationRequestParams instead")]
pub type CreateElicitationRequestParam = CreateElicitationRequestParams;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct CreateElicitationResult {
pub action: ElicitationAction,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<Value>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
impl CreateElicitationResult {
pub fn new(action: ElicitationAction) -> Self {
Self {
action,
content: None,
meta: None,
}
}
pub fn with_content(mut self, content: Value) -> Self {
self.content = Some(content);
self
}
pub fn with_meta(mut self, meta: Meta) -> Self {
self.meta = Some(meta);
self
}
}
pub type CreateElicitationRequest =
Request<ElicitationCreateRequestMethod, CreateElicitationRequestParams>;
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ElicitationResponseNotificationParam {
pub elicitation_id: String,
}
impl ElicitationResponseNotificationParam {
pub fn new(elicitation_id: impl Into<String>) -> Self {
Self {
elicitation_id: elicitation_id.into(),
}
}
}
pub type ElicitationCompletionNotification =
Notification<ElicitationCompletionNotificationMethod, ElicitationResponseNotificationParam>;
#[derive(Default, Debug, Serialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CallToolResult {
#[serde(default)]
pub content: Vec<Content>,
#[serde(skip_serializing_if = "Option::is_none")]
pub structured_content: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
}
impl<'de> Deserialize<'de> for CallToolResult {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct Helper {
content: Option<Vec<Content>>,
structured_content: Option<Value>,
is_error: Option<bool>,
#[serde(rename = "_meta")]
meta: Option<Meta>,
}
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 {
pub fn success(content: Vec<Content>) -> Self {
CallToolResult {
content,
structured_content: None,
is_error: Some(false),
meta: None,
}
}
pub fn error(content: Vec<Content>) -> Self {
CallToolResult {
content,
structured_content: None,
is_error: Some(true),
meta: None,
}
}
pub fn structured(value: Value) -> Self {
CallToolResult {
content: vec![Content::text(value.to_string())],
structured_content: Some(value),
is_error: Some(false),
meta: None,
}
}
pub fn structured_error(value: Value) -> Self {
CallToolResult {
content: vec![Content::text(value.to_string())],
structured_content: Some(value),
is_error: Some(true),
meta: None,
}
}
pub fn with_meta(mut self, meta: Option<Meta>) -> Self {
self.meta = meta;
self
}
pub fn into_typed<T>(self) -> Result<T, serde_json::Error>
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");
pub type ListToolsRequest = RequestOptionalParam<ListToolsRequestMethod, PaginatedRequestParams>;
paginated_result!(
ListToolsResult {
tools: Vec<Tool>
}
);
const_string!(CallToolRequestMethod = "tools/call");
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CallToolRequestParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub name: Cow<'static, str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<JsonObject>,
#[serde(skip_serializing_if = "Option::is_none")]
pub task: Option<JsonObject>,
}
impl CallToolRequestParams {
pub fn new(name: impl Into<Cow<'static, str>>) -> Self {
Self {
meta: None,
name: name.into(),
arguments: None,
task: None,
}
}
pub fn with_arguments(mut self, arguments: JsonObject) -> Self {
self.arguments = Some(arguments);
self
}
pub fn with_task(mut self, task: JsonObject) -> 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<Meta> {
&mut self.meta
}
}
impl TaskAugmentedRequestParamsMeta for CallToolRequestParams {
fn task(&self) -> Option<&JsonObject> {
self.task.as_ref()
}
fn task_mut(&mut self) -> &mut Option<JsonObject> {
&mut self.task
}
}
#[deprecated(since = "0.13.0", note = "Use CallToolRequestParams instead")]
pub type CallToolRequestParam = CallToolRequestParams;
pub type CallToolRequest = Request<CallToolRequestMethod, CallToolRequestParams>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct CreateMessageResult {
pub model: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_reason: Option<String>,
#[serde(flatten)]
pub message: SamplingMessage,
}
impl CreateMessageResult {
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";
pub fn with_stop_reason(mut self, stop_reason: impl Into<String>) -> Self {
self.stop_reason = Some(stop_reason.into());
self
}
pub fn with_model(mut self, model: impl Into<String>) -> Self {
self.model = model.into();
self
}
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<String>,
pub messages: Vec<PromptMessage>,
}
impl GetPromptResult {
pub fn new(messages: Vec<PromptMessage>) -> Self {
Self {
description: None,
messages,
}
}
pub fn with_description<D: Into<String>>(mut self, description: D) -> Self {
self.description = Some(description.into());
self
}
}
const_string!(GetTaskInfoMethod = "tasks/get");
pub type GetTaskInfoRequest = Request<GetTaskInfoMethod, GetTaskInfoParams>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct GetTaskInfoParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub task_id: String,
}
impl RequestParamsMeta for GetTaskInfoParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use GetTaskInfoParams instead")]
pub type GetTaskInfoParam = GetTaskInfoParams;
const_string!(ListTasksMethod = "tasks/list");
pub type ListTasksRequest = RequestOptionalParam<ListTasksMethod, PaginatedRequestParams>;
const_string!(GetTaskResultMethod = "tasks/result");
pub type GetTaskResultRequest = Request<GetTaskResultMethod, GetTaskResultParams>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct GetTaskResultParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub task_id: String,
}
impl RequestParamsMeta for GetTaskResultParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use GetTaskResultParams instead")]
pub type GetTaskResultParam = GetTaskResultParams;
const_string!(CancelTaskMethod = "tasks/cancel");
pub type CancelTaskRequest = Request<CancelTaskMethod, CancelTaskParams>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct CancelTaskParams {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
pub task_id: String,
}
impl RequestParamsMeta for CancelTaskParams {
fn meta(&self) -> Option<&Meta> {
self.meta.as_ref()
}
fn meta_mut(&mut self) -> &mut Option<Meta> {
&mut self.meta
}
}
#[deprecated(since = "0.13.0", note = "Use CancelTaskParams instead")]
pub type CancelTaskParam = CancelTaskParams;
#[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<crate::model::Task>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_cursor: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total: Option<u64>,
}
impl ListTasksResult {
pub fn new(tasks: Vec<crate::model::Task>) -> Self {
Self {
tasks,
next_cursor: None,
total: None,
}
}
}
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
| GetTaskInfoRequest
| ListTasksRequest
| GetTaskResultRequest
| 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::GetTaskInfoRequest(r) => r.method.as_str(),
ClientRequest::ListTasksRequest(r) => r.method.as_str(),
ClientRequest::GetTaskResultRequest(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
| CustomNotification;
);
ts_union!(
export type ClientResult =
box CreateMessageResult
| ListRootsResult
| CreateElicitationResult
| EmptyResult
| CustomResult;
);
impl ClientResult {
pub fn empty(_: ()) -> ClientResult {
ClientResult::EmptyResult(EmptyResult {})
}
}
pub type ClientJsonRpcMessage = JsonRpcMessage<ClientRequest, ClientResult, ClientNotification>;
ts_union!(
export type ServerRequest =
| PingRequest
| CreateMessageRequest
| ListRootsRequest
| CreateElicitationRequest
| CustomRequest;
);
ts_union!(
export type ServerNotification =
| CancelledNotification
| ProgressNotification
| LoggingMessageNotification
| ResourceUpdatedNotification
| ResourceListChangedNotification
| ToolListChangedNotification
| PromptListChangedNotification
| ElicitationCompletionNotification
| CustomNotification;
);
ts_union!(
export type ServerResult =
| InitializeResult
| CompleteResult
| GetPromptResult
| ListPromptsResult
| ListResourcesResult
| ListResourceTemplatesResult
| ReadResourceResult
| ListToolsResult
| CreateElicitationResult
| CreateTaskResult
| ListTasksResult
| GetTaskResult
| CancelTaskResult
| CallToolResult
| GetTaskPayloadResult
| EmptyResult
| CustomResult
;
);
impl ServerResult {
pub fn empty(_: ()) -> ServerResult {
ServerResult::EmptyResult(EmptyResult {})
}
}
pub type ServerJsonRpcMessage = JsonRpcMessage<ServerRequest, ServerResult, ServerNotification>;
impl TryInto<CancelledNotification> for ServerNotification {
type Error = ServerNotification;
fn try_into(self) -> Result<CancelledNotification, Self::Error> {
if let ServerNotification::CancelledNotification(t) = self {
Ok(t)
} else {
Err(self)
}
}
}
impl TryInto<CancelledNotification> for ClientNotification {
type Error = ClientNotification;
fn try_into(self) -> Result<CancelledNotification, Self::Error> {
if let ClientNotification::CancelledNotification(t) = self {
Ok(t)
} else {
Err(self)
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[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() {
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"),
}
let serialized = serde_json::to_value(&message).expect("Should serialize");
assert_eq!(serialized, negative_id_json);
let large_negative_json = json!({
"jsonrpc": "2.0",
"id": -9007199254740991i64, "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"),
}
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"),
}
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");
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() {
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,
};
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() {
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: CreateElicitationRequestParams =
serde_json::from_value(json_data_without_tag).expect("Deserialization failed");
if let CreateElicitationRequestParams::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: CreateElicitationRequestParams =
serde_json::from_value(json_data_form).expect("Deserialization failed");
if let CreateElicitationRequestParams::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: CreateElicitationRequestParams =
serde_json::from_value(json_data_url).expect("Deserialization failed");
if let CreateElicitationRequestParams::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 = CreateElicitationRequestParams::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 = CreateElicitationRequestParams::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<JsonRpcMessage, _> = serde_json::from_slice(payload);
assert!(
matches!(result, Ok(JsonRpcMessage::Notification(_))),
"Expected Ok(Notification), got: {:?}",
result
);
}
}