1use alloc::format;
36use alloc::string::{String, ToString};
37use alloc::vec;
38use alloc::vec::Vec;
39use core::fmt::Display;
40
41use serde::Serialize;
42
43use turbomcp_types::{CallToolResult, Content};
44
45pub trait IntoToolResponse {
75 fn into_tool_response(self) -> CallToolResult;
77}
78
79impl IntoToolResponse for CallToolResult {
84 #[inline]
85 fn into_tool_response(self) -> CallToolResult {
86 self
87 }
88}
89
90impl IntoToolResponse for String {
91 #[inline]
92 fn into_tool_response(self) -> CallToolResult {
93 CallToolResult::text(self)
94 }
95}
96
97impl IntoToolResponse for &str {
98 #[inline]
99 fn into_tool_response(self) -> CallToolResult {
100 CallToolResult::text(self)
101 }
102}
103
104impl IntoToolResponse for () {
105 #[inline]
106 fn into_tool_response(self) -> CallToolResult {
107 CallToolResult::default()
108 }
109}
110
111macro_rules! impl_into_tool_response_for_numeric {
113 ($($t:ty),*) => {
114 $(
115 impl IntoToolResponse for $t {
116 #[inline]
117 fn into_tool_response(self) -> CallToolResult {
118 CallToolResult::text(self.to_string())
119 }
120 }
121 )*
122 };
123}
124
125impl_into_tool_response_for_numeric!(
126 i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
127);
128
129impl IntoToolResponse for bool {
130 #[inline]
131 fn into_tool_response(self) -> CallToolResult {
132 CallToolResult::text(self.to_string())
133 }
134}
135
136impl IntoToolResponse for Content {
137 #[inline]
138 fn into_tool_response(self) -> CallToolResult {
139 CallToolResult {
140 content: vec![self],
141 ..Default::default()
142 }
143 }
144}
145
146impl IntoToolResponse for Vec<Content> {
147 #[inline]
148 fn into_tool_response(self) -> CallToolResult {
149 CallToolResult {
150 content: self,
151 ..Default::default()
152 }
153 }
154}
155
156impl<T, E> IntoToolResponse for Result<T, E>
161where
162 T: IntoToolResponse,
163 E: Into<ToolError>,
164{
165 fn into_tool_response(self) -> CallToolResult {
166 match self {
167 Ok(v) => v.into_tool_response(),
168 Err(e) => {
169 let error: ToolError = e.into();
170 error.into_tool_response()
171 }
172 }
173 }
174}
175
176#[derive(Debug, Clone)]
203pub struct Json<T>(pub T);
204
205fn encode_json_for_tool<T: Serialize>(value: &T) -> Result<(serde_json::Value, String), String> {
212 let value =
213 serde_json::to_value(value).map_err(|e| format!("JSON serialization failed: {e}"))?;
214 match serde_json::to_string_pretty(&value) {
215 Ok(json) if json.len() > crate::MAX_MESSAGE_SIZE => Err(format!(
216 "JSON output too large: {} bytes exceeds {} byte limit",
217 json.len(),
218 crate::MAX_MESSAGE_SIZE
219 )),
220 Ok(json) => Ok((value, json)),
221 Err(e) => Err(format!("JSON serialization failed: {e}")),
222 }
223}
224
225use turbomcp_types::structured_content_if_object as structured_if_object;
226
227impl<T: Serialize> IntoToolResponse for Json<T> {
228 fn into_tool_response(self) -> CallToolResult {
229 match encode_json_for_tool(&self.0) {
230 Ok((value, json)) => CallToolResult {
231 structured_content: structured_if_object(value),
232 ..CallToolResult::text(json)
233 },
234 Err(msg) => ToolError::new(msg).into_tool_response(),
235 }
236 }
237}
238
239impl<T: Serialize> turbomcp_types::IntoToolResult for Json<T> {
240 fn into_tool_result(self) -> turbomcp_types::ToolResult {
241 match encode_json_for_tool(&self.0) {
242 Ok((value, json)) => turbomcp_types::ToolResult {
243 structured_content: structured_if_object(value),
244 ..turbomcp_types::ToolResult::text(json)
245 },
246 Err(msg) => turbomcp_types::ToolResult::error(msg),
247 }
248 }
249}
250
251#[derive(Debug, Clone)]
263pub struct Text<T>(pub T);
264
265impl<T: Into<String>> IntoToolResponse for Text<T> {
266 #[inline]
267 fn into_tool_response(self) -> CallToolResult {
268 CallToolResult::text(self.0)
269 }
270}
271
272#[derive(Debug, Clone)]
285pub struct Image<D, M> {
286 pub data: D,
288 pub mime_type: M,
290}
291
292impl<D: Into<String>, M: Into<String>> IntoToolResponse for Image<D, M> {
293 #[inline]
294 fn into_tool_response(self) -> CallToolResult {
295 CallToolResult {
296 content: vec![Content::image(self.data, self.mime_type)],
297 ..Default::default()
298 }
299 }
300}
301
302#[derive(Debug, Clone)]
331pub struct ToolError {
332 message: String,
333 code: Option<i32>,
334}
335
336impl ToolError {
337 pub fn new(message: impl Into<String>) -> Self {
339 Self {
340 message: message.into(),
341 code: None,
342 }
343 }
344
345 pub fn with_code(code: i32, message: impl Into<String>) -> Self {
347 Self {
348 message: message.into(),
349 code: Some(code),
350 }
351 }
352
353 pub fn message(&self) -> &str {
355 &self.message
356 }
357
358 pub fn code(&self) -> Option<i32> {
360 self.code
361 }
362}
363
364impl IntoToolResponse for ToolError {
365 #[inline]
366 fn into_tool_response(self) -> CallToolResult {
367 CallToolResult::error(self.message)
368 }
369}
370
371impl Display for ToolError {
372 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
373 write!(f, "{}", self.message)
374 }
375}
376
377#[cfg(feature = "std")]
379impl std::error::Error for ToolError {}
380
381impl From<&str> for ToolError {
386 fn from(s: &str) -> Self {
387 Self {
388 message: s.into(),
389 code: None,
390 }
391 }
392}
393
394impl From<String> for ToolError {
395 fn from(s: String) -> Self {
396 Self {
397 message: s,
398 code: None,
399 }
400 }
401}
402
403impl From<serde_json::Error> for ToolError {
404 fn from(e: serde_json::Error) -> Self {
405 Self {
406 message: e.to_string(),
407 code: None,
408 }
409 }
410}
411
412impl From<crate::error::McpError> for ToolError {
414 fn from(e: crate::error::McpError) -> Self {
415 Self {
416 message: e.to_string(),
417 code: Some(e.jsonrpc_code()),
418 }
419 }
420}
421
422#[cfg(feature = "std")]
424impl From<std::io::Error> for ToolError {
425 fn from(e: std::io::Error) -> Self {
426 Self {
427 message: e.to_string(),
428 code: None,
429 }
430 }
431}
432
433#[cfg(feature = "std")]
434impl From<std::string::FromUtf8Error> for ToolError {
435 fn from(e: std::string::FromUtf8Error) -> Self {
436 Self {
437 message: e.to_string(),
438 code: None,
439 }
440 }
441}
442
443#[cfg(feature = "std")]
444impl From<std::num::ParseIntError> for ToolError {
445 fn from(e: std::num::ParseIntError) -> Self {
446 Self {
447 message: e.to_string(),
448 code: None,
449 }
450 }
451}
452
453#[cfg(feature = "std")]
454impl From<std::num::ParseFloatError> for ToolError {
455 fn from(e: std::num::ParseFloatError) -> Self {
456 Self {
457 message: e.to_string(),
458 code: None,
459 }
460 }
461}
462
463#[cfg(feature = "std")]
464impl From<Box<dyn std::error::Error>> for ToolError {
465 fn from(e: Box<dyn std::error::Error>) -> Self {
466 Self {
467 message: e.to_string(),
468 code: None,
469 }
470 }
471}
472
473#[cfg(feature = "std")]
474impl From<Box<dyn std::error::Error + Send + Sync>> for ToolError {
475 fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
476 Self {
477 message: e.to_string(),
478 code: None,
479 }
480 }
481}
482
483pub trait IntoToolError {
499 fn tool_err(self, context: impl Display) -> ToolError;
501}
502
503impl<E: Display> IntoToolError for E {
504 fn tool_err(self, context: impl Display) -> ToolError {
505 ToolError::new(format!("{}: {}", context, self))
506 }
507}
508
509impl<A, B> IntoToolResponse for (A, B)
514where
515 A: IntoToolResponse,
516 B: IntoToolResponse,
517{
518 fn into_tool_response(self) -> CallToolResult {
519 let a = self.0.into_tool_response();
520 let b = self.1.into_tool_response();
521
522 let mut content = a.content;
523 content.extend(b.content);
524
525 CallToolResult {
526 content,
527 is_error: a.is_error.or(b.is_error),
528 ..Default::default()
529 }
530 }
531}
532
533impl<T: IntoToolResponse> IntoToolResponse for Option<T> {
538 fn into_tool_response(self) -> CallToolResult {
539 match self {
540 Some(v) => v.into_tool_response(),
541 None => CallToolResult::text("No result"),
542 }
543 }
544}
545
546#[cfg(test)]
547mod tests {
548 use super::*;
549
550 #[test]
551 fn test_string_into_response() {
552 let response = "hello".into_tool_response();
553 assert_eq!(response.content.len(), 1);
554 assert!(response.is_error.is_none());
555 }
556
557 #[test]
558 fn test_owned_string_into_response() {
559 let response = String::from("hello").into_tool_response();
560 assert_eq!(response.content.len(), 1);
561 }
562
563 #[test]
564 fn test_json_into_response() {
565 let data = serde_json::json!({"key": "value"});
566 let response = Json(data).into_tool_response();
567 assert_eq!(response.content.len(), 1);
568 }
569
570 #[test]
571 fn test_tool_error_into_response() {
572 let error = ToolError::new("something went wrong");
573 let response = error.into_tool_response();
574 assert_eq!(response.is_error, Some(true));
575 }
576
577 #[test]
578 fn test_result_ok_into_response() {
579 let result: Result<String, ToolError> = Ok("success".into());
580 let response = result.into_tool_response();
581 assert!(response.is_error.is_none());
582 }
583
584 #[test]
585 fn test_result_err_into_response() {
586 let result: Result<String, ToolError> = Err(ToolError::new("failed"));
587 let response = result.into_tool_response();
588 assert_eq!(response.is_error, Some(true));
589 }
590
591 #[test]
592 fn test_unit_into_response() {
593 let response = ().into_tool_response();
594 assert!(response.content.is_empty());
595 }
596
597 #[test]
598 fn test_option_some_into_response() {
599 let response = Some("value").into_tool_response();
600 assert_eq!(response.content.len(), 1);
601 }
602
603 #[test]
604 fn test_option_none_into_response() {
605 let response: CallToolResult = None::<String>.into_tool_response();
606 assert_eq!(response.content.len(), 1);
607 }
608
609 #[test]
610 fn test_tuple_into_response() {
611 let response = ("first", "second").into_tool_response();
612 assert_eq!(response.content.len(), 2);
613 }
614
615 #[test]
616 fn test_text_wrapper() {
617 let response = Text("explicit text").into_tool_response();
618 assert_eq!(response.content.len(), 1);
619 }
620
621 #[test]
622 fn test_image_wrapper() {
623 let response = Image {
624 data: "base64data",
625 mime_type: "image/png",
626 }
627 .into_tool_response();
628 assert_eq!(response.content.len(), 1);
629 }
630
631 #[test]
632 fn test_numeric_types() {
633 assert_eq!(42i32.into_tool_response().content.len(), 1);
634 assert_eq!(42i64.into_tool_response().content.len(), 1);
635 assert_eq!(2.5f64.into_tool_response().content.len(), 1);
636 }
637
638 #[test]
639 fn test_bool_into_response() {
640 let true_response = true.into_tool_response();
641 let false_response = false.into_tool_response();
642 assert_eq!(true_response.content.len(), 1);
643 assert_eq!(false_response.content.len(), 1);
644 }
645
646 #[test]
647 fn test_json_size_limit_enforcement() {
648 let large_string = "x".repeat(crate::MAX_MESSAGE_SIZE + 100);
650 let large_data = serde_json::json!({ "data": large_string });
651 let response = Json(large_data).into_tool_response();
652
653 assert_eq!(response.is_error, Some(true));
655 assert_eq!(response.content.len(), 1);
656
657 if let Content::Text(text) = &response.content[0] {
659 assert!(text.text.contains("too large"));
660 assert!(text.text.contains("byte limit"));
661 } else {
662 panic!("Expected text content in error response");
663 }
664 }
665
666 #[test]
667 fn test_json_within_size_limit() {
668 let small_data = serde_json::json!({ "key": "value" });
670 let response = Json(small_data).into_tool_response();
671
672 assert!(response.is_error.is_none() || response.is_error == Some(false));
674 assert_eq!(response.content.len(), 1);
675 }
676}