1use std::sync::Arc;
2
3use async_trait::async_trait;
4use chrono::{DateTime, Utc};
5use indexmap::IndexMap;
6use parking_lot::RwLock;
7use serde::{Deserialize, Serialize};
8use serde_json::{Map, Value};
9use tracing::{trace, warn};
10use uuid::Uuid;
11
12use crate::error::{FastMcpError, Result};
13
14#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
16pub enum DuplicateBehavior {
17 #[default]
19 Error,
20 Replace,
22 Ignore,
24 Warn,
26}
27
28#[derive(Clone, Debug)]
30pub struct InvocationContext {
31 pub tool_name: String,
32 pub request_id: Uuid,
33 pub timestamp: DateTime<Utc>,
34 pub metadata: Map<String, Value>,
35}
36
37impl InvocationContext {
38 pub fn new(tool_name: impl Into<String>) -> Self {
39 Self {
40 tool_name: tool_name.into(),
41 request_id: Uuid::new_v4(),
42 timestamp: Utc::now(),
43 metadata: Map::new(),
44 }
45 }
46}
47
48pub type ToolAnnotations = Map<String, Value>;
50
51fn annotations_is_empty(annotations: &ToolAnnotations) -> bool {
52 annotations.is_empty()
53}
54
55#[derive(Clone, Debug, Serialize, Deserialize)]
57pub struct ToolResponse {
58 pub content: Vec<Value>,
59 #[serde(default, skip_serializing_if = "annotations_is_empty")]
60 pub annotations: ToolAnnotations,
61}
62
63impl ToolResponse {
64 pub fn new(content: Vec<Value>) -> Self {
65 Self {
66 content,
67 annotations: ToolAnnotations::default(),
68 }
69 }
70
71 pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
72 self.annotations = annotations;
73 self
74 }
75}
76
77#[derive(Clone, Debug, Serialize, Deserialize)]
79pub struct ToolDefinitionMetadata {
80 pub name: String,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub description: Option<String>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub summary: Option<String>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub parameters: Option<Value>,
87 #[serde(default, skip_serializing_if = "annotations_is_empty")]
88 pub annotations: ToolAnnotations,
89}
90
91#[async_trait]
93pub trait ToolInvocation: Send + Sync {
94 async fn invoke(&self, ctx: InvocationContext, arguments: Value) -> Result<ToolResponse>;
95}
96
97#[async_trait]
98impl<F, Fut> ToolInvocation for F
99where
100 F: Send + Sync + Fn(InvocationContext, Value) -> Fut,
101 Fut: std::future::Future<Output = Result<ToolResponse>> + Send,
102{
103 async fn invoke(&self, ctx: InvocationContext, arguments: Value) -> Result<ToolResponse> {
104 (self)(ctx, arguments).await
105 }
106}
107
108pub struct ToolDefinition {
110 pub name: String,
111 pub description: Option<String>,
112 pub summary: Option<String>,
113 pub parameters: Option<Value>,
114 pub annotations: ToolAnnotations,
115 handler: Arc<dyn ToolInvocation>,
116}
117
118impl ToolDefinition {
119 pub fn new(name: impl Into<String>, handler: impl ToolInvocation + 'static) -> Self {
120 Self {
121 name: name.into(),
122 description: None,
123 summary: None,
124 parameters: None,
125 annotations: ToolAnnotations::default(),
126 handler: Arc::new(handler),
127 }
128 }
129
130 pub fn with_description(mut self, description: impl Into<String>) -> Self {
131 self.description = Some(description.into());
132 self
133 }
134
135 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
136 self.summary = Some(summary.into());
137 self
138 }
139
140 pub fn with_parameters(mut self, parameters: Value) -> Self {
141 self.parameters = Some(parameters);
142 self
143 }
144
145 pub fn with_annotations(mut self, annotations: ToolAnnotations) -> Self {
146 self.annotations = annotations;
147 self
148 }
149
150 pub(crate) fn metadata(&self) -> ToolDefinitionMetadata {
151 ToolDefinitionMetadata {
152 name: self.name.clone(),
153 description: self.description.clone(),
154 summary: self.summary.clone(),
155 parameters: self.parameters.clone(),
156 annotations: self.annotations.clone(),
157 }
158 }
159
160 pub(crate) fn handler(&self) -> Arc<dyn ToolInvocation> {
161 Arc::clone(&self.handler)
162 }
163}
164
165pub struct ToolManager {
167 duplicate_behavior: DuplicateBehavior,
168 tools: RwLock<IndexMap<String, Arc<ToolDefinition>>>,
169}
170
171impl ToolManager {
172 pub fn new(duplicate_behavior: DuplicateBehavior) -> Self {
173 Self {
174 duplicate_behavior,
175 tools: RwLock::new(IndexMap::new()),
176 }
177 }
178
179 pub fn len(&self) -> usize {
180 self.tools.read().len()
181 }
182
183 pub fn is_empty(&self) -> bool {
184 self.len() == 0
185 }
186
187 pub fn register(&self, tool: ToolDefinition) -> Result<()> {
188 let mut guard = self.tools.write();
189 match guard.get_mut(&tool.name) {
190 Some(existing) => match self.duplicate_behavior {
191 DuplicateBehavior::Error => {
192 return Err(FastMcpError::DuplicateTool(tool.name));
193 }
194 DuplicateBehavior::Ignore => {
195 trace!("Ignoring duplicate registration for tool {}", tool.name);
196 }
197 DuplicateBehavior::Replace => {
198 trace!("Replacing tool {}", tool.name);
199 *existing = Arc::new(tool);
200 }
201 DuplicateBehavior::Warn => {
202 warn!("Replacing duplicate tool {}", tool.name);
203 *existing = Arc::new(tool);
204 }
205 },
206 None => {
207 guard.insert(tool.name.clone(), Arc::new(tool));
208 }
209 }
210 Ok(())
211 }
212
213 pub fn list(&self) -> Vec<ToolDefinitionMetadata> {
214 self.tools
215 .read()
216 .values()
217 .map(|tool| tool.metadata())
218 .collect()
219 }
220
221 pub fn get(&self, name: &str) -> Option<ToolDefinitionMetadata> {
222 self.tools.read().get(name).map(|tool| tool.metadata())
223 }
224
225 pub fn contains(&self, name: &str) -> bool {
226 self.tools.read().contains_key(name)
227 }
228
229 pub async fn call(&self, name: &str, arguments: Value) -> Result<ToolResponse> {
230 let tool = {
231 let guard = self.tools.read();
232 guard
233 .get(name)
234 .cloned()
235 .ok_or_else(|| FastMcpError::ToolNotFound(name.to_string()))?
236 };
237
238 let ctx = InvocationContext::new(name.to_string());
239 tool.handler().invoke(ctx, arguments).await
240 }
241}
242
243#[cfg(feature = "auto-register")]
245pub type ToolFactory = fn() -> ToolDefinition;
246
247#[cfg(feature = "auto-register")]
248#[linkme::distributed_slice]
249pub static MCP_TOOL_FACTORIES: [ToolFactory];
250
251#[cfg(feature = "auto-register")]
252pub fn register_discovered_tools(server: &crate::server::FastMcpServer) {
253 for factory in MCP_TOOL_FACTORIES {
254 if let Err(e) = server.register_tool(factory()) {
255 warn!("Auto-register tool failed: {}", e);
256 }
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use serde_json::json;
263
264 use super::*;
265
266 #[tokio::test]
267 async fn registers_and_invokes_tool() {
268 let manager = ToolManager::new(DuplicateBehavior::Error);
269
270 manager
271 .register(
272 ToolDefinition::new("greet", |_, payload: Value| async move {
273 let name = payload
274 .get("name")
275 .and_then(Value::as_str)
276 .unwrap_or("world");
277 Ok(ToolResponse::new(vec![json!({
278 "type": "text",
279 "text": format!("Hello, {name}!"),
280 })]))
281 })
282 .with_description("Greets a user"),
283 )
284 .unwrap();
285
286 let response = manager
287 .call("greet", json!({ "name": "FastMCP" }))
288 .await
289 .unwrap();
290
291 assert_eq!(response.content.len(), 1);
292 assert_eq!(
293 response.content[0]["text"].as_str(),
294 Some("Hello, FastMCP!")
295 );
296 }
297}