1use std::path::PathBuf;
2use std::time::Duration;
3
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6use thiserror::Error;
7
8#[derive(Clone, Debug, Deserialize, Serialize)]
10pub struct ToolRef {
11 pub name: String,
12 pub component: String,
13 pub entry: String,
14 #[serde(default)]
15 pub timeout_ms: Option<u64>,
16 #[serde(default)]
17 pub max_retries: Option<u32>,
18 #[serde(default)]
19 pub retry_backoff_ms: Option<u64>,
20}
21
22impl ToolRef {
23 pub fn component_path(&self) -> PathBuf {
25 PathBuf::from(&self.component)
26 }
27
28 pub fn timeout(&self) -> Option<Duration> {
30 self.timeout_ms.map(Duration::from_millis)
31 }
32
33 pub fn max_retries(&self) -> u32 {
35 self.max_retries.unwrap_or(0)
36 }
37
38 pub fn retry_backoff(&self) -> Duration {
40 Duration::from_millis(self.retry_backoff_ms.unwrap_or(200))
41 }
42}
43
44#[derive(Clone, Debug, Deserialize, Serialize)]
46pub struct ToolMapConfig {
47 pub tools: Vec<ToolRef>,
48}
49
50#[derive(Clone, Debug, Deserialize, Serialize)]
52pub struct ToolInput {
53 pub payload: Value,
54}
55
56#[derive(Clone, Debug, Deserialize, Serialize)]
58pub struct ToolOutput {
59 pub payload: Value,
60 #[serde(
61 default,
62 skip_serializing_if = "Option::is_none",
63 rename = "structuredContent"
64 )]
65 pub structured_content: Option<Value>,
66}
67
68#[derive(Debug, Error)]
70pub enum McpError {
71 #[error("tool `{0}` not found")]
72 ToolNotFound(String),
73 #[error("invalid input: {0}")]
74 InvalidInput(String),
75 #[error("execution failed: {0}")]
76 ExecutionFailed(String),
77 #[error("tool `{name}` timed out after {timeout:?}")]
78 Timeout { name: String, timeout: Duration },
79 #[error("transient failure invoking `{0}`: {1}")]
80 Transient(String, String),
81 #[error("internal error: {0}")]
82 Internal(String),
83 #[error(transparent)]
84 Io(#[from] std::io::Error),
85 #[error(transparent)]
86 Config(#[from] serde_yaml_bw::Error),
87 #[error(transparent)]
88 Json(#[from] serde_json::Error),
89}
90
91impl McpError {
92 pub fn tool_not_found(name: impl Into<String>) -> Self {
93 McpError::ToolNotFound(name.into())
94 }
95
96 pub fn timeout(name: impl Into<String>, timeout: Duration) -> Self {
97 McpError::Timeout {
98 name: name.into(),
99 timeout,
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use serde_json::json;
108
109 #[test]
110 fn captures_structured_content_in_output() {
111 let value = json!({
112 "payload": {"message": "ok"},
113 "structuredContent": {"result": "structured"}
114 });
115
116 let output: ToolOutput = serde_json::from_value(value).expect("deserialize");
117 assert_eq!(
118 output
119 .structured_content
120 .as_ref()
121 .and_then(|v| v.get("result"))
122 .and_then(Value::as_str),
123 Some("structured")
124 );
125 assert_eq!(
126 output.payload.get("message").and_then(Value::as_str),
127 Some("ok")
128 );
129 }
130}