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<String, String> {
210 match serde_json::to_string_pretty(value) {
211 Ok(json) if json.len() > crate::MAX_MESSAGE_SIZE => Err(format!(
212 "JSON output too large: {} bytes exceeds {} byte limit",
213 json.len(),
214 crate::MAX_MESSAGE_SIZE
215 )),
216 Ok(json) => Ok(json),
217 Err(e) => Err(format!("JSON serialization failed: {e}")),
218 }
219}
220
221impl<T: Serialize> IntoToolResponse for Json<T> {
222 fn into_tool_response(self) -> CallToolResult {
223 match encode_json_for_tool(&self.0) {
224 Ok(json) => CallToolResult::text(json),
225 Err(msg) => ToolError::new(msg).into_tool_response(),
226 }
227 }
228}
229
230impl<T: Serialize> turbomcp_types::IntoToolResult for Json<T> {
231 fn into_tool_result(self) -> turbomcp_types::ToolResult {
232 match encode_json_for_tool(&self.0) {
233 Ok(json) => turbomcp_types::ToolResult::text(json),
234 Err(msg) => turbomcp_types::ToolResult::error(msg),
235 }
236 }
237}
238
239#[derive(Debug, Clone)]
251pub struct Text<T>(pub T);
252
253impl<T: Into<String>> IntoToolResponse for Text<T> {
254 #[inline]
255 fn into_tool_response(self) -> CallToolResult {
256 CallToolResult::text(self.0)
257 }
258}
259
260#[derive(Debug, Clone)]
273pub struct Image<D, M> {
274 pub data: D,
276 pub mime_type: M,
278}
279
280impl<D: Into<String>, M: Into<String>> IntoToolResponse for Image<D, M> {
281 #[inline]
282 fn into_tool_response(self) -> CallToolResult {
283 CallToolResult {
284 content: vec![Content::image(self.data, self.mime_type)],
285 ..Default::default()
286 }
287 }
288}
289
290#[derive(Debug, Clone)]
319pub struct ToolError {
320 message: String,
321 code: Option<i32>,
322}
323
324impl ToolError {
325 pub fn new(message: impl Into<String>) -> Self {
327 Self {
328 message: message.into(),
329 code: None,
330 }
331 }
332
333 pub fn with_code(code: i32, message: impl Into<String>) -> Self {
335 Self {
336 message: message.into(),
337 code: Some(code),
338 }
339 }
340
341 pub fn message(&self) -> &str {
343 &self.message
344 }
345
346 pub fn code(&self) -> Option<i32> {
348 self.code
349 }
350}
351
352impl IntoToolResponse for ToolError {
353 #[inline]
354 fn into_tool_response(self) -> CallToolResult {
355 CallToolResult::error(self.message)
356 }
357}
358
359impl Display for ToolError {
360 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
361 write!(f, "{}", self.message)
362 }
363}
364
365#[cfg(feature = "std")]
367impl std::error::Error for ToolError {}
368
369impl From<&str> for ToolError {
374 fn from(s: &str) -> Self {
375 Self {
376 message: s.into(),
377 code: None,
378 }
379 }
380}
381
382impl From<String> for ToolError {
383 fn from(s: String) -> Self {
384 Self {
385 message: s,
386 code: None,
387 }
388 }
389}
390
391impl From<serde_json::Error> for ToolError {
392 fn from(e: serde_json::Error) -> Self {
393 Self {
394 message: e.to_string(),
395 code: None,
396 }
397 }
398}
399
400impl From<crate::error::McpError> for ToolError {
402 fn from(e: crate::error::McpError) -> Self {
403 Self {
404 message: e.to_string(),
405 code: Some(e.jsonrpc_code()),
406 }
407 }
408}
409
410#[cfg(feature = "std")]
412impl From<std::io::Error> for ToolError {
413 fn from(e: std::io::Error) -> Self {
414 Self {
415 message: e.to_string(),
416 code: None,
417 }
418 }
419}
420
421#[cfg(feature = "std")]
422impl From<std::string::FromUtf8Error> for ToolError {
423 fn from(e: std::string::FromUtf8Error) -> Self {
424 Self {
425 message: e.to_string(),
426 code: None,
427 }
428 }
429}
430
431#[cfg(feature = "std")]
432impl From<std::num::ParseIntError> for ToolError {
433 fn from(e: std::num::ParseIntError) -> Self {
434 Self {
435 message: e.to_string(),
436 code: None,
437 }
438 }
439}
440
441#[cfg(feature = "std")]
442impl From<std::num::ParseFloatError> for ToolError {
443 fn from(e: std::num::ParseFloatError) -> Self {
444 Self {
445 message: e.to_string(),
446 code: None,
447 }
448 }
449}
450
451#[cfg(feature = "std")]
452impl From<Box<dyn std::error::Error>> for ToolError {
453 fn from(e: Box<dyn std::error::Error>) -> Self {
454 Self {
455 message: e.to_string(),
456 code: None,
457 }
458 }
459}
460
461#[cfg(feature = "std")]
462impl From<Box<dyn std::error::Error + Send + Sync>> for ToolError {
463 fn from(e: Box<dyn std::error::Error + Send + Sync>) -> Self {
464 Self {
465 message: e.to_string(),
466 code: None,
467 }
468 }
469}
470
471pub trait IntoToolError {
487 fn tool_err(self, context: impl Display) -> ToolError;
489}
490
491impl<E: Display> IntoToolError for E {
492 fn tool_err(self, context: impl Display) -> ToolError {
493 ToolError::new(format!("{}: {}", context, self))
494 }
495}
496
497impl<A, B> IntoToolResponse for (A, B)
502where
503 A: IntoToolResponse,
504 B: IntoToolResponse,
505{
506 fn into_tool_response(self) -> CallToolResult {
507 let a = self.0.into_tool_response();
508 let b = self.1.into_tool_response();
509
510 let mut content = a.content;
511 content.extend(b.content);
512
513 CallToolResult {
514 content,
515 is_error: a.is_error.or(b.is_error),
516 ..Default::default()
517 }
518 }
519}
520
521impl<T: IntoToolResponse> IntoToolResponse for Option<T> {
526 fn into_tool_response(self) -> CallToolResult {
527 match self {
528 Some(v) => v.into_tool_response(),
529 None => CallToolResult::text("No result"),
530 }
531 }
532}
533
534#[cfg(test)]
535mod tests {
536 use super::*;
537
538 #[test]
539 fn test_string_into_response() {
540 let response = "hello".into_tool_response();
541 assert_eq!(response.content.len(), 1);
542 assert!(response.is_error.is_none());
543 }
544
545 #[test]
546 fn test_owned_string_into_response() {
547 let response = String::from("hello").into_tool_response();
548 assert_eq!(response.content.len(), 1);
549 }
550
551 #[test]
552 fn test_json_into_response() {
553 let data = serde_json::json!({"key": "value"});
554 let response = Json(data).into_tool_response();
555 assert_eq!(response.content.len(), 1);
556 }
557
558 #[test]
559 fn test_tool_error_into_response() {
560 let error = ToolError::new("something went wrong");
561 let response = error.into_tool_response();
562 assert_eq!(response.is_error, Some(true));
563 }
564
565 #[test]
566 fn test_result_ok_into_response() {
567 let result: Result<String, ToolError> = Ok("success".into());
568 let response = result.into_tool_response();
569 assert!(response.is_error.is_none());
570 }
571
572 #[test]
573 fn test_result_err_into_response() {
574 let result: Result<String, ToolError> = Err(ToolError::new("failed"));
575 let response = result.into_tool_response();
576 assert_eq!(response.is_error, Some(true));
577 }
578
579 #[test]
580 fn test_unit_into_response() {
581 let response = ().into_tool_response();
582 assert!(response.content.is_empty());
583 }
584
585 #[test]
586 fn test_option_some_into_response() {
587 let response = Some("value").into_tool_response();
588 assert_eq!(response.content.len(), 1);
589 }
590
591 #[test]
592 fn test_option_none_into_response() {
593 let response: CallToolResult = None::<String>.into_tool_response();
594 assert_eq!(response.content.len(), 1);
595 }
596
597 #[test]
598 fn test_tuple_into_response() {
599 let response = ("first", "second").into_tool_response();
600 assert_eq!(response.content.len(), 2);
601 }
602
603 #[test]
604 fn test_text_wrapper() {
605 let response = Text("explicit text").into_tool_response();
606 assert_eq!(response.content.len(), 1);
607 }
608
609 #[test]
610 fn test_image_wrapper() {
611 let response = Image {
612 data: "base64data",
613 mime_type: "image/png",
614 }
615 .into_tool_response();
616 assert_eq!(response.content.len(), 1);
617 }
618
619 #[test]
620 fn test_numeric_types() {
621 assert_eq!(42i32.into_tool_response().content.len(), 1);
622 assert_eq!(42i64.into_tool_response().content.len(), 1);
623 assert_eq!(2.5f64.into_tool_response().content.len(), 1);
624 }
625
626 #[test]
627 fn test_bool_into_response() {
628 let true_response = true.into_tool_response();
629 let false_response = false.into_tool_response();
630 assert_eq!(true_response.content.len(), 1);
631 assert_eq!(false_response.content.len(), 1);
632 }
633
634 #[test]
635 fn test_json_size_limit_enforcement() {
636 let large_string = "x".repeat(crate::MAX_MESSAGE_SIZE + 100);
638 let large_data = serde_json::json!({ "data": large_string });
639 let response = Json(large_data).into_tool_response();
640
641 assert_eq!(response.is_error, Some(true));
643 assert_eq!(response.content.len(), 1);
644
645 if let Content::Text(text) = &response.content[0] {
647 assert!(text.text.contains("too large"));
648 assert!(text.text.contains("byte limit"));
649 } else {
650 panic!("Expected text content in error response");
651 }
652 }
653
654 #[test]
655 fn test_json_within_size_limit() {
656 let small_data = serde_json::json!({ "key": "value" });
658 let response = Json(small_data).into_tool_response();
659
660 assert!(response.is_error.is_none() || response.is_error == Some(false));
662 assert_eq!(response.content.len(), 1);
663 }
664}