use std::borrow::Cow;
use std::convert::Infallible;
use std::fmt;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use pin_project_lite::pin_project;
use schemars::{JsonSchema, Schema, SchemaGenerator};
use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::{Map, Value};
#[cfg(feature = "stateless")]
use tower::ServiceExt;
use tower::util::BoxCloneService;
use tower_service::Service;
#[cfg(feature = "stateless")]
use tokio::sync::Mutex;
use crate::context::{Extensions, RequestContext};
use crate::error::{Error, Result, ResultExt};
use crate::protocol::{
CallToolResult, ClientCapabilities, InputRequests, InputResponses, RequestOutcome, TaskStatus,
TaskSupportMode, ToolAnnotations, ToolDefinition, ToolExecution, ToolIcon,
};
#[derive(Debug, Clone)]
pub struct ToolRequest {
pub ctx: RequestContext,
pub args: Value,
}
impl ToolRequest {
pub fn new(ctx: RequestContext, args: Value) -> Self {
Self { ctx, args }
}
}
pub type BoxToolService = BoxCloneService<ToolRequest, CallToolResult, Infallible>;
#[cfg(feature = "stateless")]
type BoxMrtrToolService = BoxCloneService<ToolRequest, RequestOutcome<CallToolResult>, Infallible>;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct NoParams;
impl<'de> serde::Deserialize<'de> for NoParams {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct NoParamsVisitor;
impl<'de> serde::de::Visitor<'de> for NoParamsVisitor {
type Value = NoParams;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("null or an object")
}
fn visit_unit<E>(self) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(NoParams)
}
fn visit_none<E>(self) -> std::result::Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(NoParams)
}
fn visit_some<D>(self, deserializer: D) -> std::result::Result<Self::Value, D::Error>
where
D: serde::Deserializer<'de>,
{
serde::Deserialize::deserialize(deserializer)
}
fn visit_map<A>(self, mut map: A) -> std::result::Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
while map
.next_entry::<serde::de::IgnoredAny, serde::de::IgnoredAny>()?
.is_some()
{}
Ok(NoParams)
}
}
deserializer.deserialize_any(NoParamsVisitor)
}
}
impl JsonSchema for NoParams {
fn schema_name() -> Cow<'static, str> {
Cow::Borrowed("NoParams")
}
fn json_schema(_generator: &mut SchemaGenerator) -> Schema {
serde_json::json!({
"type": "object"
})
.try_into()
.expect("valid schema")
}
}
pub(crate) fn validate_tool_name(name: &str) -> Result<()> {
if name.is_empty() {
return Err(Error::tool("Tool name cannot be empty"));
}
if name.len() > 64 {
return Err(Error::tool(format!(
"Tool name '{}' exceeds maximum length of 64 characters (got {})",
name,
name.len()
)));
}
if let Some(invalid_char) = name
.chars()
.find(|c| !c.is_ascii_alphanumeric() && *c != '_' && *c != '-' && *c != '.' && *c != '/')
{
return Err(Error::tool(format!(
"Tool name '{}' contains invalid character '{}'. Only alphanumeric, underscore, hyphen, dot, and forward slash are allowed.",
name, invalid_char
)));
}
Ok(())
}
pub(crate) fn ensure_object_schema(mut schema: Value) -> Value {
if let Some(obj) = schema.as_object_mut()
&& !obj.contains_key("type")
{
obj.insert("type".to_string(), serde_json::json!("object"));
}
schema
}
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[async_trait::async_trait]
pub(crate) trait LiveToolHandler: Send + Sync {
async fn call(
&self,
ctx: RequestContext,
task: TaskContext,
arguments: Value,
) -> Result<TaskOutcome>;
}
fn live_input_schema<I: JsonSchema>() -> Value {
serde_json::to_value(schemars::schema_for!(I))
.unwrap_or_else(|_| serde_json::json!({"type": "object"}))
}
struct FnLiveToolHandlerWithContext<I, F> {
handler: F,
_input: std::marker::PhantomData<fn() -> I>,
}
#[async_trait::async_trait]
impl<I, F, Fut> LiveToolHandler for FnLiveToolHandlerWithContext<I, F>
where
I: DeserializeOwned + Send + Sync + 'static,
F: Fn(RequestContext, TaskContext, I) -> Fut + Send + Sync,
Fut: Future<Output = Result<TaskOutcome>> + Send,
{
async fn call(
&self,
ctx: RequestContext,
task: TaskContext,
arguments: Value,
) -> Result<TaskOutcome> {
let input: I = serde_json::from_value(arguments).map_err(|e| {
crate::error::Error::Tool(crate::error::ToolError::new(format!(
"invalid arguments: {e}"
)))
})?;
(self.handler)(ctx, task, input).await
}
}
struct GuardedLiveToolHandler<G> {
guard: G,
inner: Arc<dyn LiveToolHandler>,
}
#[async_trait::async_trait]
impl<G> LiveToolHandler for GuardedLiveToolHandler<G>
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
{
async fn call(
&self,
ctx: RequestContext,
task: TaskContext,
arguments: Value,
) -> Result<TaskOutcome> {
let request = ToolRequest::new(ctx, arguments);
match (self.guard)(&request) {
Ok(()) => self.inner.call(request.ctx, task, request.args).await,
Err(message) => Ok(TaskOutcome::Completed(CallToolResult::error(message))),
}
}
}
struct FnLiveToolHandler<I, F> {
handler: F,
_input: std::marker::PhantomData<fn() -> I>,
}
#[async_trait::async_trait]
impl<I, F, Fut> LiveToolHandler for FnLiveToolHandler<I, F>
where
I: DeserializeOwned + Send + Sync + 'static,
F: Fn(TaskContext, I) -> Fut + Send + Sync,
Fut: Future<Output = Result<TaskOutcome>> + Send,
{
async fn call(
&self,
_ctx: RequestContext,
task: TaskContext,
arguments: Value,
) -> Result<TaskOutcome> {
let input: I = serde_json::from_value(arguments).map_err(|e| {
crate::error::Error::Tool(crate::error::ToolError::new(format!(
"invalid arguments: {e}"
)))
})?;
(self.handler)(task, input).await
}
}
pub trait ToolHandler: Send + Sync {
fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>>;
fn call_with_context(
&self,
_ctx: RequestContext,
args: Value,
) -> BoxFuture<'_, Result<CallToolResult>> {
self.call(args)
}
fn uses_context(&self) -> bool {
false
}
fn input_schema(&self) -> Value;
}
#[cfg(feature = "stateless")]
pub trait MrtrToolHandler: Send + Sync {
fn call(
&self,
ctx: RequestContext,
args: Value,
) -> BoxFuture<'_, Result<RequestOutcome<CallToolResult>>>;
fn input_schema(&self) -> Value;
}
#[cfg(feature = "stateless")]
struct MrtrToolHandlerService<H> {
handler: Arc<H>,
}
#[cfg(feature = "stateless")]
impl<H> MrtrToolHandlerService<H> {
fn new(handler: H) -> Self {
Self {
handler: Arc::new(handler),
}
}
}
#[cfg(feature = "stateless")]
impl<H> Clone for MrtrToolHandlerService<H> {
fn clone(&self) -> Self {
Self {
handler: self.handler.clone(),
}
}
}
#[cfg(feature = "stateless")]
impl<H> Service<ToolRequest> for MrtrToolHandlerService<H>
where
H: MrtrToolHandler + 'static,
{
type Response = RequestOutcome<CallToolResult>;
type Error = Error;
type Future =
Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: ToolRequest) -> Self::Future {
let handler = self.handler.clone();
Box::pin(async move { handler.call(req.ctx, req.args).await })
}
}
#[cfg(feature = "stateless")]
struct ServiceMrtrToolHandler {
service: Mutex<BoxMrtrToolService>,
input_schema: Value,
}
#[cfg(feature = "stateless")]
struct GuardedMrtrToolHandler<G> {
guard: G,
inner: Arc<dyn MrtrToolHandler>,
}
#[cfg(feature = "stateless")]
impl<G> MrtrToolHandler for GuardedMrtrToolHandler<G>
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
{
fn call(
&self,
ctx: RequestContext,
args: Value,
) -> BoxFuture<'_, Result<RequestOutcome<CallToolResult>>> {
let request = ToolRequest::new(ctx, args);
match (self.guard)(&request) {
Ok(()) => self.inner.call(request.ctx, request.args),
Err(message) => {
Box::pin(
async move { Ok(RequestOutcome::Complete(CallToolResult::error(message))) },
)
}
}
}
fn input_schema(&self) -> Value {
self.inner.input_schema()
}
}
#[cfg(feature = "stateless")]
impl MrtrToolHandler for ServiceMrtrToolHandler {
fn call(
&self,
ctx: RequestContext,
args: Value,
) -> BoxFuture<'_, Result<RequestOutcome<CallToolResult>>> {
Box::pin(async move {
let mut service = self.service.lock().await.clone();
let outcome = service
.ready()
.await
.expect("MRTR tool service is infallible")
.call(ToolRequest::new(ctx, args))
.await
.expect("MRTR tool service is infallible");
Ok(outcome)
})
}
fn input_schema(&self) -> Value {
self.input_schema.clone()
}
}
pub(crate) struct ToolHandlerService<H> {
handler: Arc<H>,
}
impl<H> ToolHandlerService<H> {
pub(crate) fn new(handler: H) -> Self {
Self {
handler: Arc::new(handler),
}
}
}
impl<H> Clone for ToolHandlerService<H> {
fn clone(&self) -> Self {
Self {
handler: self.handler.clone(),
}
}
}
impl<H> Service<ToolRequest> for ToolHandlerService<H>
where
H: ToolHandler + 'static,
{
type Response = CallToolResult;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = std::result::Result<CallToolResult, Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: ToolRequest) -> Self::Future {
let handler = self.handler.clone();
Box::pin(async move { handler.call_with_context(req.ctx, req.args).await })
}
}
pub struct Tool {
pub name: String,
pub title: Option<String>,
pub description: Option<String>,
pub output_schema: Option<Value>,
pub icons: Option<Vec<ToolIcon>>,
pub annotations: Option<ToolAnnotations>,
pub meta: Option<Value>,
pub task_support: TaskSupportMode,
pub(crate) required_client_capabilities: Option<ClientCapabilities>,
pub(crate) task_preparer: Option<Arc<dyn TaskPreparer>>,
pub(crate) service: Option<BoxToolService>,
#[cfg(feature = "stateless")]
pub(crate) mrtr_handler: Option<Arc<dyn MrtrToolHandler>>,
pub(crate) live_handler: Option<Arc<dyn LiveToolHandler>>,
pub(crate) input_schema: Value,
}
impl std::fmt::Debug for Tool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Tool")
.field("name", &self.name)
.field("title", &self.title)
.field("description", &self.description)
.field("output_schema", &self.output_schema)
.field("icons", &self.icons)
.field("annotations", &self.annotations)
.field("meta", &self.meta)
.field("task_support", &self.task_support)
.field(
"required_client_capabilities",
&self.required_client_capabilities,
)
.finish_non_exhaustive()
}
}
unsafe impl Send for Tool {}
unsafe impl Sync for Tool {}
impl Clone for Tool {
fn clone(&self) -> Self {
Self {
live_handler: self.live_handler.clone(),
name: self.name.clone(),
title: self.title.clone(),
description: self.description.clone(),
output_schema: self.output_schema.clone(),
icons: self.icons.clone(),
annotations: self.annotations.clone(),
meta: self.meta.clone(),
task_support: self.task_support,
required_client_capabilities: self.required_client_capabilities.clone(),
task_preparer: self.task_preparer.clone(),
service: self.service.clone(),
#[cfg(feature = "stateless")]
mrtr_handler: self.mrtr_handler.clone(),
input_schema: self.input_schema.clone(),
}
}
}
impl Tool {
pub fn builder(name: impl Into<String>) -> ToolBuilder {
ToolBuilder::new(name)
}
pub fn definition(&self) -> ToolDefinition {
let execution = match self.task_support {
TaskSupportMode::Forbidden => None,
mode => Some(ToolExecution {
task_support: Some(mode),
}),
};
ToolDefinition {
name: self.name.clone(),
title: self.title.clone(),
description: self.description.clone(),
input_schema: self.input_schema.clone(),
output_schema: self.output_schema.clone(),
icons: self.icons.clone(),
annotations: self.annotations.clone(),
execution,
meta: self.meta.clone(),
}
}
pub fn with_meta(
mut self,
meta: Value,
) -> std::result::Result<Self, crate::protocol::MetaValidationError> {
crate::protocol::validate_meta_object(&meta)?;
self.meta = Some(meta);
Ok(self)
}
pub fn call(&self, args: Value) -> BoxFuture<'static, CallToolResult> {
let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
self.call_with_context(ctx, args)
}
pub fn call_with_context(
&self,
ctx: RequestContext,
args: Value,
) -> BoxFuture<'static, CallToolResult> {
let tool = self.clone();
Box::pin(async move {
match tool.call_outcome_with_context(ctx, args).await {
Ok(RequestOutcome::Complete(result)) => result,
Ok(RequestOutcome::InputRequired(_)) => CallToolResult::error(
"tool requires additional client input; use call_outcome_with_context",
),
Err(error) => CallToolResult::error(error.to_string()),
}
})
}
pub fn call_outcome(
&self,
args: Value,
) -> BoxFuture<'static, Result<RequestOutcome<CallToolResult>>> {
let ctx = RequestContext::new(crate::protocol::RequestId::Number(0));
self.call_outcome_with_context(ctx, args)
}
pub fn call_outcome_with_context(
&self,
ctx: RequestContext,
args: Value,
) -> BoxFuture<'static, Result<RequestOutcome<CallToolResult>>> {
use tower::ServiceExt;
#[cfg(feature = "stateless")]
if let Some(handler) = self.mrtr_handler.clone() {
return Box::pin(async move { handler.call(ctx, args).await });
}
let Some(service) = self.service.clone() else {
let error = Error::tool(
"tool has no synchronous or MRTR handler; it can only be invoked as a task",
);
return Box::pin(async move { Err(error) });
};
Box::pin(async move {
let result = service.oneshot(ToolRequest::new(ctx, args)).await.unwrap();
Ok(RequestOutcome::Complete(result))
})
}
pub fn require_client_capabilities(mut self, required: ClientCapabilities) -> Self {
self.required_client_capabilities = Some(required);
self
}
pub fn required_client_capabilities(&self) -> Option<&ClientCapabilities> {
self.required_client_capabilities.as_ref()
}
pub fn with_task_preparation<F, Fut>(mut self, prepare: F) -> Self
where
F: Fn(TaskContext, Value) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<TaskPreparation>> + Send + 'static,
{
self.task_preparer = Some(Arc::new(prepare));
self
}
pub fn with_typed_task_preparation<I, F, Fut>(mut self, prepare: F) -> Self
where
I: DeserializeOwned + Send + Sync + 'static,
F: Fn(TaskContext, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<TaskPreparation>> + Send + 'static,
{
self.task_preparer = Some(Arc::new(TypedTaskPreparer {
prepare,
_phantom: std::marker::PhantomData,
}));
self
}
pub(crate) async fn prepare_task(
&self,
context: TaskContext,
arguments: Value,
) -> Result<TaskPreparation> {
match self.task_preparer.as_ref() {
Some(prepare) => prepare.prepare(context, arguments).await,
None => Ok(TaskPreparation::default()),
}
}
pub fn with_guard<G>(self, guard: G) -> Self
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
{
let live_handler = self.live_handler.clone().map(|inner| {
Arc::new(GuardedLiveToolHandler {
guard: guard.clone(),
inner,
}) as Arc<dyn LiveToolHandler>
});
#[cfg(feature = "stateless")]
if let Some(inner) = self.mrtr_handler.clone() {
return Tool {
live_handler,
mrtr_handler: Some(Arc::new(GuardedMrtrToolHandler { guard, inner })),
..self
};
}
match self.service.clone() {
Some(service) => {
let guarded = GuardService {
guard,
inner: service,
};
let caught = ToolCatchError::new(guarded);
Tool {
live_handler,
service: Some(BoxCloneService::new(caught)),
..self
}
}
None if live_handler.is_some() => Tool {
live_handler,
..self
},
None => panic!("tool must have a complete, MRTR, or live handler"),
}
}
pub fn with_name_prefix(&self, prefix: &str) -> Self {
Self {
live_handler: self.live_handler.clone(),
name: format!("{}.{}", prefix, self.name),
title: self.title.clone(),
description: self.description.clone(),
output_schema: self.output_schema.clone(),
icons: self.icons.clone(),
annotations: self.annotations.clone(),
meta: self.meta.clone(),
task_support: self.task_support,
required_client_capabilities: self.required_client_capabilities.clone(),
task_preparer: self.task_preparer.clone(),
service: self.service.clone(),
#[cfg(feature = "stateless")]
mrtr_handler: self.mrtr_handler.clone(),
input_schema: self.input_schema.clone(),
}
}
#[allow(clippy::too_many_arguments)]
fn from_handler<H: ToolHandler + 'static>(
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
input_schema_override: Option<Value>,
handler: H,
) -> Self {
let input_schema =
ensure_object_schema(input_schema_override.unwrap_or_else(|| handler.input_schema()));
let handler_service = ToolHandlerService::new(handler);
let catch_error = ToolCatchError::new(handler_service);
let service = BoxCloneService::new(catch_error);
Self {
live_handler: None,
name,
title,
description,
output_schema,
icons,
annotations,
meta: None,
task_support,
required_client_capabilities: None,
task_preparer: None,
service: Some(service),
#[cfg(feature = "stateless")]
mrtr_handler: None,
input_schema,
}
}
#[cfg(feature = "stateless")]
#[allow(clippy::too_many_arguments)]
fn from_mrtr_handler<H: MrtrToolHandler + 'static>(
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
input_schema_override: Option<Value>,
handler: H,
) -> Self {
let input_schema =
ensure_object_schema(input_schema_override.unwrap_or_else(|| handler.input_schema()));
Self {
live_handler: None,
name,
title,
description,
output_schema,
icons,
annotations,
meta: None,
task_support,
required_client_capabilities: None,
task_preparer: None,
service: None,
mrtr_handler: Some(Arc::new(handler)),
input_schema,
}
}
}
pub struct ToolBuilder {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema_override: Option<Value>,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
}
impl ToolBuilder {
pub fn new(name: impl Into<String>) -> Self {
let name = name.into();
if let Err(e) = validate_tool_name(&name) {
panic!("{e}");
}
Self {
name,
title: None,
description: None,
output_schema: None,
input_schema_override: None,
icons: None,
annotations: None,
task_support: TaskSupportMode::default(),
}
}
pub fn try_new(name: impl Into<String>) -> Result<Self> {
let name = name.into();
validate_tool_name(&name)?;
Ok(Self {
name,
title: None,
description: None,
output_schema: None,
input_schema_override: None,
icons: None,
annotations: None,
task_support: TaskSupportMode::default(),
})
}
pub fn title(mut self, title: impl Into<String>) -> Self {
self.title = Some(title.into());
self
}
pub fn output_schema(mut self, schema: Value) -> Self {
self.output_schema = Some(schema);
self
}
pub fn input_schema(mut self, schema: Value) -> Self {
self.input_schema_override = Some(schema);
self
}
pub fn icon(mut self, src: impl Into<String>) -> Self {
self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
src: src.into(),
mime_type: None,
sizes: None,
theme: None,
});
self
}
pub fn icon_with_meta(
mut self,
src: impl Into<String>,
mime_type: Option<String>,
sizes: Option<Vec<String>>,
) -> Self {
self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
src: src.into(),
mime_type,
sizes,
theme: None,
});
self
}
pub fn description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn read_only(mut self) -> Self {
self.annotations
.get_or_insert_with(ToolAnnotations::default)
.read_only_hint = true;
self
}
pub fn non_destructive(mut self) -> Self {
self.annotations
.get_or_insert_with(ToolAnnotations::default)
.destructive_hint = false;
self
}
pub fn destructive(mut self) -> Self {
self.annotations
.get_or_insert_with(ToolAnnotations::default)
.destructive_hint = true;
self
}
pub fn idempotent(mut self) -> Self {
self.annotations
.get_or_insert_with(ToolAnnotations::default)
.idempotent_hint = true;
self
}
pub fn read_only_safe(mut self) -> Self {
let ann = self
.annotations
.get_or_insert_with(ToolAnnotations::default);
ann.read_only_hint = true;
ann.idempotent_hint = true;
ann.destructive_hint = false;
self
}
pub fn annotations(mut self, annotations: ToolAnnotations) -> Self {
self.annotations = Some(annotations);
self
}
pub fn task_support(mut self, mode: TaskSupportMode) -> Self {
self.task_support = mode;
self
}
pub fn no_params_handler<F, Fut>(self, handler: F) -> ToolBuilderWithNoParamsHandler<F>
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
{
ToolBuilderWithNoParamsHandler {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
handler,
}
}
pub fn handler<I, F, Fut>(self, handler: F) -> ToolBuilderWithHandler<I, F>
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
{
ToolBuilderWithHandler {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
task_preparer: None,
handler,
_phantom: std::marker::PhantomData,
}
}
pub fn live_task_handler<I, F, Fut>(self, handler: F) -> ToolBuilderWithLiveHandler
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(TaskContext, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<TaskOutcome>> + Send + 'static,
{
self.into_live(
Arc::new(FnLiveToolHandler {
handler,
_input: std::marker::PhantomData::<fn() -> I>,
}),
live_input_schema::<I>(),
)
}
pub fn live_task_handler_with_context<I, F, Fut>(self, handler: F) -> ToolBuilderWithLiveHandler
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(RequestContext, TaskContext, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<TaskOutcome>> + Send + 'static,
{
self.into_live(
Arc::new(FnLiveToolHandlerWithContext {
handler,
_input: std::marker::PhantomData::<fn() -> I>,
}),
live_input_schema::<I>(),
)
}
fn into_live(
self,
handler: Arc<dyn LiveToolHandler>,
derived_schema: Value,
) -> ToolBuilderWithLiveHandler {
ToolBuilderWithLiveHandler {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema: self.input_schema_override.unwrap_or(derived_schema),
icons: self.icons,
annotations: self.annotations,
handler,
}
}
#[cfg(feature = "stateless")]
pub fn mrtr_handler<I, F, Fut>(self, handler: F) -> ToolBuilderWithMrtrHandler<I, F>
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(RequestContext, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<RequestOutcome<CallToolResult>>> + Send + 'static,
{
ToolBuilderWithMrtrHandler {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
handler,
_phantom: std::marker::PhantomData,
}
}
pub fn extractor_handler<S, F, T>(
self,
state: S,
handler: F,
) -> crate::extract::ToolBuilderWithExtractor<S, F, T>
where
S: Clone + Send + Sync + 'static,
F: crate::extract::ExtractorHandler<S, T> + Clone,
T: Send + Sync + 'static,
{
let input_schema = ensure_object_schema(
self.input_schema_override
.unwrap_or_else(|| F::input_schema()),
);
crate::extract::ToolBuilderWithExtractor {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
state,
handler,
input_schema,
_phantom: std::marker::PhantomData,
}
}
#[deprecated(
since = "0.8.0",
note = "Use `extractor_handler` instead -- it auto-detects JSON schema from `Json<T>` extractors without requiring a turbofish"
)]
#[allow(deprecated)]
pub fn extractor_handler_typed<S, F, T, I>(
self,
state: S,
handler: F,
) -> crate::extract::ToolBuilderWithTypedExtractor<S, F, T, I>
where
S: Clone + Send + Sync + 'static,
F: crate::extract::TypedExtractorHandler<S, T, I> + Clone,
T: Send + Sync + 'static,
I: schemars::JsonSchema + Send + Sync + 'static,
{
crate::extract::ToolBuilderWithTypedExtractor {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
state,
handler,
_phantom: std::marker::PhantomData,
}
}
}
struct NoParamsTypedHandler<F> {
handler: F,
}
impl<F, Fut> ToolHandler for NoParamsTypedHandler<F>
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
{
fn call(&self, _args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
Box::pin(async move { (self.handler)().await })
}
fn input_schema(&self) -> Value {
serde_json::json!({ "type": "object" })
}
}
#[doc(hidden)]
pub struct ToolBuilderWithHandler<I, F> {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema_override: Option<Value>,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
task_preparer: Option<Arc<dyn TaskPreparer>>,
handler: F,
_phantom: std::marker::PhantomData<I>,
}
#[cfg(feature = "stateless")]
#[doc(hidden)]
pub struct ToolBuilderWithMrtrHandler<I, F> {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema_override: Option<Value>,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
handler: F,
_phantom: std::marker::PhantomData<I>,
}
pub struct ToolBuilderWithLiveHandler {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema: Value,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
handler: Arc<dyn LiveToolHandler>,
}
impl ToolBuilderWithLiveHandler {
pub fn build(self) -> Tool {
Tool {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
icons: self.icons,
annotations: self.annotations,
meta: None,
task_support: TaskSupportMode::Required,
required_client_capabilities: None,
task_preparer: None,
service: None,
#[cfg(feature = "stateless")]
mrtr_handler: None,
live_handler: Some(self.handler),
input_schema: ensure_object_schema(self.input_schema),
}
}
pub fn fallback_handler<I, F, Fut>(self, handler: F) -> ToolBuilderWithLiveAndFallback
where
I: DeserializeOwned + Send + Sync + 'static,
F: Fn(I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
{
let service = ToolHandlerService::new(LiveFallbackHandler {
handler,
_phantom: std::marker::PhantomData::<fn() -> I>,
});
let caught = ToolCatchError::new(service);
ToolBuilderWithLiveAndFallback {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema: self.input_schema,
icons: self.icons,
annotations: self.annotations,
task_support: TaskSupportMode::Optional,
live_handler: self.handler,
fallback: BoxCloneService::new(caught),
}
}
#[cfg(feature = "stateless")]
pub fn fallback_mrtr_handler<I, F, Fut>(self, handler: F) -> ToolBuilderWithLiveAndMrtrFallback
where
I: DeserializeOwned + Send + Sync + 'static,
F: Fn(RequestContext, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<RequestOutcome<CallToolResult>>> + Send + 'static,
{
ToolBuilderWithLiveAndMrtrFallback {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema: self.input_schema,
icons: self.icons,
annotations: self.annotations,
task_support: TaskSupportMode::Optional,
live_handler: self.handler,
fallback: Arc::new(LiveFallbackMrtrHandler {
handler,
_phantom: std::marker::PhantomData::<fn() -> I>,
}),
}
}
}
struct LiveFallbackHandler<I, F> {
handler: F,
_phantom: std::marker::PhantomData<fn() -> I>,
}
impl<I, F, Fut> ToolHandler for LiveFallbackHandler<I, F>
where
I: DeserializeOwned + Send + Sync + 'static,
F: Fn(I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
{
fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
Box::pin(async move {
let input: I = match serde_json::from_value(args) {
Ok(input) => input,
Err(e) => return Ok(CallToolResult::error(format!("Invalid input: {e}"))),
};
(self.handler)(input).await
})
}
fn input_schema(&self) -> Value {
serde_json::json!({ "type": "object" })
}
}
#[cfg(feature = "stateless")]
struct LiveFallbackMrtrHandler<I, F> {
handler: F,
_phantom: std::marker::PhantomData<fn() -> I>,
}
#[cfg(feature = "stateless")]
impl<I, F, Fut> MrtrToolHandler for LiveFallbackMrtrHandler<I, F>
where
I: DeserializeOwned + Send + Sync + 'static,
F: Fn(RequestContext, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<RequestOutcome<CallToolResult>>> + Send + 'static,
{
fn call(
&self,
ctx: RequestContext,
args: Value,
) -> BoxFuture<'_, Result<RequestOutcome<CallToolResult>>> {
Box::pin(async move {
let input: I = serde_json::from_value(args)
.map_err(|error| Error::invalid_params(format!("Invalid input: {error}")))?;
(self.handler)(ctx, input).await
})
}
fn input_schema(&self) -> Value {
serde_json::json!({ "type": "object" })
}
}
pub struct ToolBuilderWithLiveAndFallback {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema: Value,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
live_handler: Arc<dyn LiveToolHandler>,
fallback: BoxToolService,
}
impl ToolBuilderWithLiveAndFallback {
pub fn task_support(mut self, mode: TaskSupportMode) -> Self {
self.task_support = mode;
self
}
pub fn build(self) -> Tool {
Tool {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
icons: self.icons,
annotations: self.annotations,
meta: None,
task_support: self.task_support,
required_client_capabilities: None,
task_preparer: None,
service: Some(self.fallback),
#[cfg(feature = "stateless")]
mrtr_handler: None,
live_handler: Some(self.live_handler),
input_schema: ensure_object_schema(self.input_schema),
}
}
}
#[cfg(feature = "stateless")]
pub struct ToolBuilderWithLiveAndMrtrFallback {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema: Value,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
live_handler: Arc<dyn LiveToolHandler>,
fallback: Arc<dyn MrtrToolHandler>,
}
#[cfg(feature = "stateless")]
impl ToolBuilderWithLiveAndMrtrFallback {
pub fn task_support(mut self, mode: TaskSupportMode) -> Self {
self.task_support = mode;
self
}
pub fn build(self) -> Tool {
Tool {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
icons: self.icons,
annotations: self.annotations,
meta: None,
task_support: self.task_support,
required_client_capabilities: None,
task_preparer: None,
service: None,
mrtr_handler: Some(self.fallback),
live_handler: Some(self.live_handler),
input_schema: ensure_object_schema(self.input_schema),
}
}
}
#[cfg(feature = "stateless")]
#[doc(hidden)]
pub struct ToolBuilderWithMrtrLayer<I, F, L> {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema_override: Option<Value>,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
handler: F,
layer: L,
_phantom: std::marker::PhantomData<I>,
}
#[doc(hidden)]
pub struct ToolBuilderWithNoParamsHandler<F> {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema_override: Option<Value>,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
handler: F,
}
impl<F, Fut> ToolBuilderWithNoParamsHandler<F>
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
{
pub fn build(self) -> Tool {
Tool::from_handler(
self.name,
self.title,
self.description,
self.output_schema,
self.icons,
self.annotations,
self.task_support,
self.input_schema_override,
NoParamsTypedHandler {
handler: self.handler,
},
)
}
pub fn layer<L>(self, layer: L) -> ToolBuilderWithNoParamsHandlerLayer<F, L> {
ToolBuilderWithNoParamsHandlerLayer {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
handler: self.handler,
layer,
}
}
pub fn guard<G>(self, guard: G) -> ToolBuilderWithNoParamsHandlerLayer<F, GuardLayer<G>>
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
{
self.layer(GuardLayer::new(guard))
}
}
#[doc(hidden)]
pub struct ToolBuilderWithNoParamsHandlerLayer<F, L> {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema_override: Option<Value>,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
handler: F,
layer: L,
}
#[allow(private_bounds)]
impl<F, Fut, L> ToolBuilderWithNoParamsHandlerLayer<F, L>
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
L: tower::Layer<ToolHandlerService<NoParamsTypedHandler<F>>> + Clone + Send + Sync + 'static,
L::Service: Service<ToolRequest, Response = CallToolResult> + Clone + Send + 'static,
<L::Service as Service<ToolRequest>>::Error: fmt::Display + Send,
<L::Service as Service<ToolRequest>>::Future: Send,
{
pub fn build(self) -> Tool {
let input_schema = ensure_object_schema(
self.input_schema_override
.unwrap_or_else(|| serde_json::json!({ "type": "object" })),
);
let handler_service = ToolHandlerService::new(NoParamsTypedHandler {
handler: self.handler,
});
let layered = self.layer.layer(handler_service);
let catch_error = ToolCatchError::new(layered);
let service = BoxCloneService::new(catch_error);
Tool {
live_handler: None,
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
icons: self.icons,
annotations: self.annotations,
meta: None,
task_support: self.task_support,
required_client_capabilities: None,
task_preparer: None,
service: Some(service),
#[cfg(feature = "stateless")]
mrtr_handler: None,
input_schema,
}
}
pub fn layer<L2>(
self,
layer: L2,
) -> ToolBuilderWithNoParamsHandlerLayer<F, tower::layer::util::Stack<L2, L>> {
ToolBuilderWithNoParamsHandlerLayer {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
handler: self.handler,
layer: tower::layer::util::Stack::new(layer, self.layer),
}
}
pub fn guard<G>(
self,
guard: G,
) -> ToolBuilderWithNoParamsHandlerLayer<F, tower::layer::util::Stack<GuardLayer<G>, L>>
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
{
self.layer(GuardLayer::new(guard))
}
}
impl<I, F, Fut> ToolBuilderWithHandler<I, F>
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
{
pub fn build(self) -> Tool {
let mut tool = Tool::from_handler(
self.name,
self.title,
self.description,
self.output_schema,
self.icons,
self.annotations,
self.task_support,
self.input_schema_override,
TypedHandler {
handler: self.handler,
_phantom: std::marker::PhantomData,
},
);
tool.task_preparer = self.task_preparer;
tool
}
pub fn task_preparation<P, PrepareFuture>(mut self, prepare: P) -> Self
where
P: Fn(TaskContext, I) -> PrepareFuture + Send + Sync + 'static,
PrepareFuture: Future<Output = Result<TaskPreparation>> + Send + 'static,
{
self.task_preparer = Some(Arc::new(TypedTaskPreparer {
prepare,
_phantom: std::marker::PhantomData,
}));
self
}
pub fn layer<L>(self, layer: L) -> ToolBuilderWithLayer<I, F, L> {
ToolBuilderWithLayer {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
task_preparer: self.task_preparer,
handler: self.handler,
layer,
_phantom: std::marker::PhantomData,
}
}
pub fn guard<G>(self, guard: G) -> ToolBuilderWithLayer<I, F, GuardLayer<G>>
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
{
self.layer(GuardLayer::new(guard))
}
}
#[cfg(feature = "stateless")]
impl<I, F, Fut> ToolBuilderWithMrtrHandler<I, F>
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(RequestContext, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<RequestOutcome<CallToolResult>>> + Send + 'static,
{
pub fn build(self) -> Tool {
Tool::from_mrtr_handler(
self.name,
self.title,
self.description,
self.output_schema,
self.icons,
self.annotations,
self.task_support,
self.input_schema_override,
TypedMrtrHandler {
handler: self.handler,
_phantom: std::marker::PhantomData,
},
)
}
pub fn layer<L>(self, layer: L) -> ToolBuilderWithMrtrLayer<I, F, L> {
ToolBuilderWithMrtrLayer {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
handler: self.handler,
layer,
_phantom: std::marker::PhantomData,
}
}
pub fn guard<G>(self, guard: G) -> ToolBuilderWithMrtrLayer<I, F, GuardLayer<G>>
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
{
self.layer(GuardLayer::new(guard))
}
}
#[cfg(feature = "stateless")]
#[allow(private_bounds)]
impl<I, F, Fut, L> ToolBuilderWithMrtrLayer<I, F, L>
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(RequestContext, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<RequestOutcome<CallToolResult>>> + Send + 'static,
L: tower::Layer<MrtrToolHandlerService<TypedMrtrHandler<I, F>>> + Clone + Send + Sync + 'static,
L::Service:
Service<ToolRequest, Response = RequestOutcome<CallToolResult>> + Clone + Send + 'static,
<L::Service as Service<ToolRequest>>::Error: fmt::Display + Send + 'static,
<L::Service as Service<ToolRequest>>::Future: Send + 'static,
{
pub fn build(self) -> Tool {
let input_schema = self.input_schema_override.unwrap_or_else(|| {
let schema = schemars::schema_for!(I);
serde_json::to_value(schema).unwrap_or_else(|_| serde_json::json!({ "type": "object" }))
});
let input_schema = ensure_object_schema(input_schema);
let service = MrtrToolHandlerService::new(TypedMrtrHandler {
handler: self.handler,
_phantom: std::marker::PhantomData,
});
let service = self.layer.layer(service);
let service = BoxCloneService::new(MrtrToolCatchError::new(service));
Tool {
live_handler: None,
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
icons: self.icons,
annotations: self.annotations,
meta: None,
task_support: self.task_support,
required_client_capabilities: None,
task_preparer: None,
service: None,
mrtr_handler: Some(Arc::new(ServiceMrtrToolHandler {
service: Mutex::new(service),
input_schema: input_schema.clone(),
})),
input_schema,
}
}
pub fn layer<L2>(
self,
layer: L2,
) -> ToolBuilderWithMrtrLayer<I, F, tower::layer::util::Stack<L2, L>> {
ToolBuilderWithMrtrLayer {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
handler: self.handler,
layer: tower::layer::util::Stack::new(layer, self.layer),
_phantom: std::marker::PhantomData,
}
}
pub fn guard<G>(
self,
guard: G,
) -> ToolBuilderWithMrtrLayer<I, F, tower::layer::util::Stack<GuardLayer<G>, L>>
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
{
self.layer(GuardLayer::new(guard))
}
}
#[doc(hidden)]
pub struct ToolBuilderWithLayer<I, F, L> {
name: String,
title: Option<String>,
description: Option<String>,
output_schema: Option<Value>,
input_schema_override: Option<Value>,
icons: Option<Vec<ToolIcon>>,
annotations: Option<ToolAnnotations>,
task_support: TaskSupportMode,
task_preparer: Option<Arc<dyn TaskPreparer>>,
handler: F,
layer: L,
_phantom: std::marker::PhantomData<I>,
}
#[allow(private_bounds)]
impl<I, F, Fut, L> ToolBuilderWithLayer<I, F, L>
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
L: tower::Layer<ToolHandlerService<TypedHandler<I, F>>> + Clone + Send + Sync + 'static,
L::Service: Service<ToolRequest, Response = CallToolResult> + Clone + Send + 'static,
<L::Service as Service<ToolRequest>>::Error: fmt::Display + Send,
<L::Service as Service<ToolRequest>>::Future: Send,
{
pub fn build(self) -> Tool {
let input_schema = self.input_schema_override.unwrap_or_else(|| {
let input_schema = schemars::schema_for!(I);
serde_json::to_value(input_schema)
.unwrap_or_else(|_| serde_json::json!({ "type": "object" }))
});
let input_schema = ensure_object_schema(input_schema);
let handler_service = ToolHandlerService::new(TypedHandler {
handler: self.handler,
_phantom: std::marker::PhantomData,
});
let layered = self.layer.layer(handler_service);
let catch_error = ToolCatchError::new(layered);
let service = BoxCloneService::new(catch_error);
Tool {
live_handler: None,
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
icons: self.icons,
annotations: self.annotations,
meta: None,
task_support: self.task_support,
required_client_capabilities: None,
task_preparer: self.task_preparer,
service: Some(service),
#[cfg(feature = "stateless")]
mrtr_handler: None,
input_schema,
}
}
pub fn layer<L2>(
self,
layer: L2,
) -> ToolBuilderWithLayer<I, F, tower::layer::util::Stack<L2, L>> {
ToolBuilderWithLayer {
name: self.name,
title: self.title,
description: self.description,
output_schema: self.output_schema,
input_schema_override: self.input_schema_override,
icons: self.icons,
annotations: self.annotations,
task_support: self.task_support,
task_preparer: self.task_preparer,
handler: self.handler,
layer: tower::layer::util::Stack::new(layer, self.layer),
_phantom: std::marker::PhantomData,
}
}
pub fn guard<G>(
self,
guard: G,
) -> ToolBuilderWithLayer<I, F, tower::layer::util::Stack<GuardLayer<G>, L>>
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
{
self.layer(GuardLayer::new(guard))
}
}
struct TypedHandler<I, F> {
handler: F,
_phantom: std::marker::PhantomData<I>,
}
#[cfg(feature = "stateless")]
struct TypedMrtrHandler<I, F> {
handler: F,
_phantom: std::marker::PhantomData<I>,
}
#[cfg(feature = "stateless")]
impl<I, F, Fut> MrtrToolHandler for TypedMrtrHandler<I, F>
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(RequestContext, I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<RequestOutcome<CallToolResult>>> + Send + 'static,
{
fn call(
&self,
ctx: RequestContext,
args: Value,
) -> BoxFuture<'_, Result<RequestOutcome<CallToolResult>>> {
Box::pin(async move {
let input: I = serde_json::from_value(args)
.map_err(|error| Error::invalid_params(format!("Invalid input: {error}")))?;
(self.handler)(ctx, input).await
})
}
fn input_schema(&self) -> Value {
let schema = schemars::schema_for!(I);
ensure_object_schema(
serde_json::to_value(schema)
.unwrap_or_else(|_| serde_json::json!({ "type": "object" })),
)
}
}
impl<I, F, Fut> ToolHandler for TypedHandler<I, F>
where
I: JsonSchema + DeserializeOwned + Send + Sync + 'static,
F: Fn(I) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<CallToolResult>> + Send + 'static,
{
fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
Box::pin(async move {
let input: I = match serde_json::from_value(args) {
Ok(input) => input,
Err(e) => return Ok(CallToolResult::error(format!("Invalid input: {e}"))),
};
(self.handler)(input).await
})
}
fn input_schema(&self) -> Value {
let schema = schemars::schema_for!(I);
let schema = serde_json::to_value(schema).unwrap_or_else(|_| {
serde_json::json!({
"type": "object"
})
});
ensure_object_schema(schema)
}
}
pub trait McpTool: Send + Sync + 'static {
const NAME: &'static str;
const DESCRIPTION: &'static str;
type Input: JsonSchema + DeserializeOwned + Send;
type Output: Serialize + Send;
fn call(&self, input: Self::Input) -> impl Future<Output = Result<Self::Output>> + Send;
fn annotations(&self) -> Option<ToolAnnotations> {
None
}
fn into_tool(self) -> Tool
where
Self: Sized,
{
if let Err(e) = validate_tool_name(Self::NAME) {
panic!("{e}");
}
let annotations = self.annotations();
let tool = Arc::new(self);
Tool::from_handler(
Self::NAME.to_string(),
None,
Some(Self::DESCRIPTION.to_string()),
None,
None,
annotations,
TaskSupportMode::default(),
None,
McpToolHandler { tool },
)
}
}
struct McpToolHandler<T: McpTool> {
tool: Arc<T>,
}
impl<T: McpTool> ToolHandler for McpToolHandler<T> {
fn call(&self, args: Value) -> BoxFuture<'_, Result<CallToolResult>> {
let tool = self.tool.clone();
Box::pin(async move {
let input: T::Input = match serde_json::from_value(args) {
Ok(input) => input,
Err(e) => return Ok(CallToolResult::error(format!("Invalid input: {e}"))),
};
let output = tool.call(input).await?;
let value = serde_json::to_value(output).tool_context("Failed to serialize output")?;
Ok(CallToolResult::json(value))
})
}
fn input_schema(&self) -> Value {
let schema = schemars::schema_for!(T::Input);
let schema = serde_json::to_value(schema).unwrap_or_else(|_| {
serde_json::json!({
"type": "object"
})
});
ensure_object_schema(schema)
}
}
mod service;
mod task;
pub use service::{GuardLayer, GuardService, ToolCatchError};
use task::TypedTaskPreparer;
pub(crate) use task::{LiveTask, TaskPreparer};
pub use task::{PendingInput, TaskContext, TaskOutcome, TaskPreparation};
#[cfg(feature = "stateless")]
use service::MrtrToolCatchError;
#[cfg(test)]
mod tests;