use std::{borrow::Cow, sync::Arc}; use futures::{FutureExt, future::BoxFuture}; use schemars::JsonSchema; use crate::{ handler::server::tool::{ CallToolHandler, DynCallToolHandler, ToolCallContext, schema_for_type, validate_against_schema, }, model::{CallToolResult, Tool, ToolAnnotations}, }; pub struct ToolRoute { #[allow(clippy::type_complexity)] pub call: Arc>, pub attr: crate::model::Tool, } impl std::fmt::Debug for ToolRoute { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ToolRoute") .field("name", &self.attr.name) .field("description", &self.attr.description) .field("input_schema", &self.attr.input_schema) .finish() } } impl Clone for ToolRoute { fn clone(&self) -> Self { Self { call: self.call.clone(), attr: self.attr.clone(), } } } impl ToolRoute { pub fn new(attr: impl Into, call: C) -> Self where C: CallToolHandler + Send + Sync + Clone + 'static, { Self { call: Arc::new(move |context: ToolCallContext| { let call = call.clone(); context.invoke(call).boxed() }), attr: attr.into(), } } pub fn new_dyn(attr: impl Into, call: C) -> Self where C: for<'a> Fn( ToolCallContext<'a, S>, ) -> BoxFuture<'a, Result> + Send + Sync + 'static, { Self { call: Arc::new(call), attr: attr.into(), } } pub fn name(&self) -> &str { &self.attr.name } } pub trait IntoToolRoute { fn into_tool_route(self) -> ToolRoute; } impl IntoToolRoute for (T, C) where S: Send + Sync + 'static, C: CallToolHandler + Send + Sync + Clone + 'static, T: Into, { fn into_tool_route(self) -> ToolRoute { ToolRoute::new(self.0.into(), self.1) } } impl IntoToolRoute for ToolRoute where S: Send + Sync + 'static, { fn into_tool_route(self) -> ToolRoute { self } } pub struct ToolAttrGenerateFunctionAdapter; impl IntoToolRoute for F where S: Send + Sync + 'static, F: Fn() -> ToolRoute, { fn into_tool_route(self) -> ToolRoute { (self)() } } pub trait CallToolHandlerExt: Sized where Self: CallToolHandler + Send + Sync + Clone + 'static, { fn name(self, name: impl Into>) -> WithToolAttr; } impl CallToolHandlerExt for C where C: CallToolHandler + Send + Sync + Clone + 'static, { fn name(self, name: impl Into>) -> WithToolAttr { WithToolAttr { attr: Tool::new( name.into(), "", schema_for_type::(), ), call: self, _marker: std::marker::PhantomData, } } } pub struct WithToolAttr where C: CallToolHandler + Send + Sync + Clone + 'static, { pub attr: crate::model::Tool, pub call: C, pub _marker: std::marker::PhantomData, } impl IntoToolRoute for WithToolAttr where C: CallToolHandler + Send + Sync + Clone + 'static, S: Send + Sync + 'static, { fn into_tool_route(self) -> ToolRoute { ToolRoute::new(self.attr, self.call) } } impl WithToolAttr where C: CallToolHandler + Send + Sync + Clone + 'static, { pub fn description(mut self, description: impl Into>) -> Self { self.attr.description = Some(description.into()); self } pub fn parameters(mut self) -> Self { self.attr.input_schema = schema_for_type::().into(); self } pub fn parameters_value(mut self, schema: serde_json::Value) -> Self { self.attr.input_schema = crate::model::object(schema).into(); self } pub fn annotation(mut self, annotation: impl Into) -> Self { self.attr.annotations = Some(annotation.into()); self } } #[derive(Debug)] pub struct ToolRouter { #[allow(clippy::type_complexity)] pub map: std::collections::HashMap, ToolRoute>, pub transparent_when_not_found: bool, } impl Default for ToolRouter { fn default() -> Self { Self { map: std::collections::HashMap::new(), transparent_when_not_found: false, } } } impl Clone for ToolRouter { fn clone(&self) -> Self { Self { map: self.map.clone(), transparent_when_not_found: self.transparent_when_not_found, } } } impl IntoIterator for ToolRouter { type Item = ToolRoute; type IntoIter = std::collections::hash_map::IntoValues, ToolRoute>; fn into_iter(self) -> Self::IntoIter { self.map.into_values() } } impl ToolRouter where S: Send + Sync + 'static, { pub fn new() -> Self { Self { map: std::collections::HashMap::new(), transparent_when_not_found: false, } } pub fn with_route(mut self, route: R) -> Self where R: IntoToolRoute, { self.add_route(route.into_tool_route()); self } pub fn add_route(&mut self, item: ToolRoute) { self.map.insert(item.attr.name.clone(), item); } pub fn merge(&mut self, other: ToolRouter) { for item in other.map.into_values() { self.add_route(item); } } pub fn remove_route(&mut self, name: &str) { self.map.remove(name); } pub fn has_route(&self, name: &str) -> bool { self.map.contains_key(name) } pub async fn call( &self, context: ToolCallContext<'_, S>, ) -> Result { let item = self .map .get(context.name()) .ok_or_else(|| crate::ErrorData::invalid_params("tool not found", None))?; let result = (item.call)(context).await?; // Validate structured content against output schema if present if let Some(ref output_schema) = item.attr.output_schema { // When output_schema is defined, structured_content is required if result.structured_content.is_none() { return Err(crate::ErrorData::invalid_params( "Tool with output_schema must return structured_content", None, )); } // Validate the structured content against the schema validate_against_schema(result.structured_content.as_ref().unwrap(), output_schema)?; } Ok(result) } pub fn list_all(&self) -> Vec { self.map.values().map(|item| item.attr.clone()).collect() } } impl std::ops::Add> for ToolRouter where S: Send + Sync + 'static, { type Output = Self; fn add(mut self, other: ToolRouter) -> Self::Output { self.merge(other); self } } impl std::ops::AddAssign> for ToolRouter where S: Send + Sync + 'static, { fn add_assign(&mut self, other: ToolRouter) { self.merge(other); } }