use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::fmt::Display;
use serde::Serialize;
use crate::types::content::Content;
use crate::types::tools::CallToolResult;
pub trait IntoToolResponse {
fn into_tool_response(self) -> CallToolResult;
}
impl IntoToolResponse for CallToolResult {
#[inline]
fn into_tool_response(self) -> CallToolResult {
self
}
}
impl IntoToolResponse for String {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult::text(self)
}
}
impl IntoToolResponse for &str {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult::text(self)
}
}
impl IntoToolResponse for () {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult {
content: vec![],
is_error: None,
_meta: None,
}
}
}
macro_rules! impl_into_tool_response_for_numeric {
($($t:ty),*) => {
$(
impl IntoToolResponse for $t {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult::text(self.to_string())
}
}
)*
};
}
impl_into_tool_response_for_numeric!(
i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
);
impl IntoToolResponse for bool {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult::text(self.to_string())
}
}
impl IntoToolResponse for Content {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult {
content: vec![self],
is_error: None,
_meta: None,
}
}
}
impl IntoToolResponse for Vec<Content> {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult {
content: self,
is_error: None,
_meta: None,
}
}
}
impl<T, E> IntoToolResponse for Result<T, E>
where
T: IntoToolResponse,
E: Into<ToolError>,
{
fn into_tool_response(self) -> CallToolResult {
match self {
Ok(v) => v.into_tool_response(),
Err(e) => {
let error: ToolError = e.into();
error.into_tool_response()
}
}
}
}
#[derive(Debug, Clone)]
pub struct Json<T>(pub T);
impl<T: Serialize> IntoToolResponse for Json<T> {
fn into_tool_response(self) -> CallToolResult {
match serde_json::to_string_pretty(&self.0) {
Ok(json) => {
if json.len() > crate::MAX_MESSAGE_SIZE {
return ToolError::new(format!(
"JSON output too large: {} bytes exceeds {} byte limit",
json.len(),
crate::MAX_MESSAGE_SIZE
))
.into_tool_response();
}
CallToolResult::text(json)
}
Err(e) => {
ToolError::new(format!("JSON serialization failed: {e}")).into_tool_response()
}
}
}
}
impl<T: Serialize> turbomcp_types::IntoToolResult for Json<T> {
fn into_tool_result(self) -> turbomcp_types::ToolResult {
match serde_json::to_string_pretty(&self.0) {
Ok(json) => {
if json.len() > crate::MAX_MESSAGE_SIZE {
return turbomcp_types::ToolResult::error(format!(
"JSON output too large: {} bytes exceeds {} byte limit",
json.len(),
crate::MAX_MESSAGE_SIZE
));
}
turbomcp_types::ToolResult::text(json)
}
Err(e) => turbomcp_types::ToolResult::error(format!("JSON serialization failed: {e}")),
}
}
}
#[derive(Debug, Clone)]
pub struct Text<T>(pub T);
impl<T: Into<String>> IntoToolResponse for Text<T> {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult::text(self.0)
}
}
#[derive(Debug, Clone)]
pub struct Image<D, M> {
pub data: D,
pub mime_type: M,
}
impl<D: Into<String>, M: Into<String>> IntoToolResponse for Image<D, M> {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult {
content: vec![Content::image(self.data, self.mime_type)],
is_error: None,
_meta: None,
}
}
}
#[derive(Debug, Clone)]
pub struct ToolError {
message: String,
code: Option<i32>,
}
impl ToolError {
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
code: None,
}
}
pub fn with_code(code: i32, message: impl Into<String>) -> Self {
Self {
message: message.into(),
code: Some(code),
}
}
pub fn message(&self) -> &str {
&self.message
}
pub fn code(&self) -> Option<i32> {
self.code
}
}
impl IntoToolResponse for ToolError {
#[inline]
fn into_tool_response(self) -> CallToolResult {
CallToolResult::error(self.message)
}
}
impl Display for ToolError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.message)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ToolError {}
impl From<&str> for ToolError {
fn from(s: &str) -> Self {
Self {
message: s.into(),
code: None,
}
}
}
impl From<String> for ToolError {
fn from(s: String) -> Self {
Self {
message: s,
code: None,
}
}
}
impl From<serde_json::Error> for ToolError {
fn from(e: serde_json::Error) -> Self {
Self {
message: e.to_string(),
code: None,
}
}
}
impl From<crate::error::McpError> for ToolError {
fn from(e: crate::error::McpError) -> Self {
Self {
message: e.to_string(),
code: Some(e.jsonrpc_code()),
}
}
}
#[cfg(feature = "std")]
impl From<std::io::Error> for ToolError {
fn from(e: std::io::Error) -> Self {
Self {
message: e.to_string(),
code: None,
}
}
}
#[cfg(feature = "std")]
impl From<std::string::FromUtf8Error> for ToolError {
fn from(e: std::string::FromUtf8Error) -> Self {
Self {
message: e.to_string(),
code: None,
}
}
}
#[cfg(feature = "std")]
impl From<std::num::ParseIntError> for ToolError {
fn from(e: std::num::ParseIntError) -> Self {
Self {
message: e.to_string(),
code: None,
}
}
}
#[cfg(feature = "std")]
impl From<std::num::ParseFloatError> for ToolError {
fn from(e: std::num::ParseFloatError) -> Self {
Self {
message: e.to_string(),
code: None,
}
}
}
#[cfg(feature = "std")]
impl From<Box<dyn std::error::Error>> for ToolError {
fn from(e: Box<dyn std::error::Error>) -> Self {
Self {
message: e.to_string(),
code: None,
}
}
}
#[cfg(feature = "std")]
impl From<Box<dyn std::error::Error + Send + Sync>> for ToolError {
fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
Self {
message: e.to_string(),
code: None,
}
}
}
pub trait IntoToolError {
fn tool_err(self, context: impl Display) -> ToolError;
}
impl<E: Display> IntoToolError for E {
fn tool_err(self, context: impl Display) -> ToolError {
ToolError::new(format!("{}: {}", context, self))
}
}
impl<A, B> IntoToolResponse for (A, B)
where
A: IntoToolResponse,
B: IntoToolResponse,
{
fn into_tool_response(self) -> CallToolResult {
let a = self.0.into_tool_response();
let b = self.1.into_tool_response();
let mut content = a.content;
content.extend(b.content);
CallToolResult {
content,
is_error: a.is_error.or(b.is_error),
_meta: None,
}
}
}
impl<T: IntoToolResponse> IntoToolResponse for Option<T> {
fn into_tool_response(self) -> CallToolResult {
match self {
Some(v) => v.into_tool_response(),
None => CallToolResult::text("No result"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_string_into_response() {
let response = "hello".into_tool_response();
assert_eq!(response.content.len(), 1);
assert!(response.is_error.is_none());
}
#[test]
fn test_owned_string_into_response() {
let response = String::from("hello").into_tool_response();
assert_eq!(response.content.len(), 1);
}
#[test]
fn test_json_into_response() {
let data = serde_json::json!({"key": "value"});
let response = Json(data).into_tool_response();
assert_eq!(response.content.len(), 1);
}
#[test]
fn test_tool_error_into_response() {
let error = ToolError::new("something went wrong");
let response = error.into_tool_response();
assert_eq!(response.is_error, Some(true));
}
#[test]
fn test_result_ok_into_response() {
let result: Result<String, ToolError> = Ok("success".into());
let response = result.into_tool_response();
assert!(response.is_error.is_none());
}
#[test]
fn test_result_err_into_response() {
let result: Result<String, ToolError> = Err(ToolError::new("failed"));
let response = result.into_tool_response();
assert_eq!(response.is_error, Some(true));
}
#[test]
fn test_unit_into_response() {
let response = ().into_tool_response();
assert!(response.content.is_empty());
}
#[test]
fn test_option_some_into_response() {
let response = Some("value").into_tool_response();
assert_eq!(response.content.len(), 1);
}
#[test]
fn test_option_none_into_response() {
let response: CallToolResult = None::<String>.into_tool_response();
assert_eq!(response.content.len(), 1);
}
#[test]
fn test_tuple_into_response() {
let response = ("first", "second").into_tool_response();
assert_eq!(response.content.len(), 2);
}
#[test]
fn test_text_wrapper() {
let response = Text("explicit text").into_tool_response();
assert_eq!(response.content.len(), 1);
}
#[test]
fn test_image_wrapper() {
let response = Image {
data: "base64data",
mime_type: "image/png",
}
.into_tool_response();
assert_eq!(response.content.len(), 1);
}
#[test]
fn test_numeric_types() {
assert_eq!(42i32.into_tool_response().content.len(), 1);
assert_eq!(42i64.into_tool_response().content.len(), 1);
assert_eq!(2.5f64.into_tool_response().content.len(), 1);
}
#[test]
fn test_bool_into_response() {
let true_response = true.into_tool_response();
let false_response = false.into_tool_response();
assert_eq!(true_response.content.len(), 1);
assert_eq!(false_response.content.len(), 1);
}
#[test]
fn test_json_size_limit_enforcement() {
let large_string = "x".repeat(crate::MAX_MESSAGE_SIZE + 100);
let large_data = serde_json::json!({ "data": large_string });
let response = Json(large_data).into_tool_response();
assert_eq!(response.is_error, Some(true));
assert_eq!(response.content.len(), 1);
if let Content::Text { text, .. } = &response.content[0] {
assert!(text.contains("too large"));
assert!(text.contains("byte limit"));
} else {
panic!("Expected text content in error response");
}
}
#[test]
fn test_json_within_size_limit() {
let small_data = serde_json::json!({ "key": "value" });
let response = Json(small_data).into_tool_response();
assert!(response.is_error.is_none() || response.is_error == Some(false));
assert_eq!(response.content.len(), 1);
}
}