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>;
#[doc(hidden)]
pub struct ToolCatchError<S> {
inner: S,
}
impl<S> ToolCatchError<S> {
pub fn new(inner: S) -> Self {
Self { inner }
}
}
impl<S: Clone> Clone for ToolCatchError<S> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<S: fmt::Debug> fmt::Debug for ToolCatchError<S> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ToolCatchError")
.field("inner", &self.inner)
.finish()
}
}
pin_project! {
#[doc(hidden)]
pub struct ToolCatchErrorFuture<F> {
#[pin]
inner: F,
}
}
impl<F, E> Future for ToolCatchErrorFuture<F>
where
F: Future<Output = std::result::Result<CallToolResult, E>>,
E: fmt::Display,
{
type Output = std::result::Result<CallToolResult, Infallible>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project().inner.poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(result)) => Poll::Ready(Ok(result)),
Poll::Ready(Err(err)) => Poll::Ready(Ok(CallToolResult::error(err.to_string()))),
}
}
}
impl<S> Service<ToolRequest> for ToolCatchError<S>
where
S: Service<ToolRequest, Response = CallToolResult> + Clone + Send + 'static,
S::Error: fmt::Display + Send,
S::Future: Send,
{
type Response = CallToolResult;
type Error = Infallible;
type Future = ToolCatchErrorFuture<S::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
match self.inner.poll_ready(cx) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
Poll::Pending => Poll::Pending,
}
}
fn call(&mut self, req: ToolRequest) -> Self::Future {
ToolCatchErrorFuture {
inner: self.inner.call(req),
}
}
}
#[cfg(feature = "stateless")]
#[derive(Clone)]
struct MrtrToolCatchError<S> {
inner: S,
}
#[cfg(feature = "stateless")]
impl<S> MrtrToolCatchError<S> {
fn new(inner: S) -> Self {
Self { inner }
}
}
#[cfg(feature = "stateless")]
impl<S> Service<ToolRequest> for MrtrToolCatchError<S>
where
S: Service<ToolRequest, Response = RequestOutcome<CallToolResult>> + Clone + Send + 'static,
S::Error: fmt::Display + Send + 'static,
S::Future: Send + 'static,
{
type Response = RequestOutcome<CallToolResult>;
type Error = Infallible;
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>> {
match self.inner.poll_ready(cx) {
Poll::Ready(Ok(())) | Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
Poll::Pending => Poll::Pending,
}
}
fn call(&mut self, req: ToolRequest) -> Self::Future {
let future = self.inner.call(req);
Box::pin(async move {
Ok(match future.await {
Ok(outcome) => outcome,
Err(error) => RequestOutcome::Complete(CallToolResult::error(error.to_string())),
})
})
}
}
#[derive(Clone)]
pub struct GuardLayer<G> {
guard: G,
}
impl<G> GuardLayer<G> {
pub fn new(guard: G) -> Self {
Self { guard }
}
}
impl<G, S> tower::Layer<S> for GuardLayer<G>
where
G: Clone,
{
type Service = GuardService<G, S>;
fn layer(&self, inner: S) -> Self::Service {
GuardService {
guard: self.guard.clone(),
inner,
}
}
}
#[doc(hidden)]
#[derive(Clone)]
pub struct GuardService<G, S> {
guard: G,
inner: S,
}
impl<G, S, R> Service<ToolRequest> for GuardService<G, S>
where
G: Fn(&ToolRequest) -> std::result::Result<(), String> + Clone + Send + Sync + 'static,
S: Service<ToolRequest, Response = R> + Clone + Send + 'static,
S::Error: Into<Error> + Send,
S::Future: Send,
R: Send + 'static,
{
type Response = R;
type Error = Error;
type Future = Pin<Box<dyn Future<Output = std::result::Result<R, Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, req: ToolRequest) -> Self::Future {
match (self.guard)(&req) {
Ok(()) => {
let fut = self.inner.call(req);
Box::pin(async move { fut.await.map_err(Into::into) })
}
Err(msg) => Box::pin(async move { Err(Error::tool(msg)) }),
}
}
}
#[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 struct TaskContext {
task_id: String,
live: Option<Arc<LiveTask>>,
}
impl std::fmt::Debug for TaskContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TaskContext")
.field("task_id", &self.task_id)
.field("live", &self.live.is_some())
.finish()
}
}
impl PartialEq for TaskContext {
fn eq(&self, other: &Self) -> bool {
self.task_id == other.task_id
}
}
impl Eq for TaskContext {}
pub(crate) struct LiveTask {
pub(crate) store: Arc<dyn crate::async_task::TaskStore>,
pub(crate) error_policy: crate::router::TaskErrorPolicy,
pub(crate) input_ready: tokio::sync::Notify,
pub(crate) cancelled: crate::context::CancellationToken,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum TaskOutcome {
Completed(CallToolResult),
Failed(crate::error::JsonRpcError),
Cancelled {
message: Option<String>,
},
}
impl TaskContext {
pub(crate) fn new(task_id: String) -> Self {
Self {
task_id,
live: None,
}
}
pub(crate) fn with_live(task_id: String, live: Arc<LiveTask>) -> Self {
Self {
task_id,
live: Some(live),
}
}
pub fn task_id(&self) -> &str {
&self.task_id
}
pub fn is_live(&self) -> bool {
self.live.is_some()
}
pub async fn require_input(&self, requests: InputRequests) -> Result<InputResponses> {
self.park_input(requests).await?.wait().await
}
pub async fn require_input_with_message(
&self,
requests: InputRequests,
message: impl Into<String>,
) -> Result<InputResponses> {
self.park_input_with_message(requests, message)
.await?
.wait()
.await
}
pub async fn park_input(&self, requests: InputRequests) -> Result<PendingInput> {
self.park_input_inner(requests, None).await
}
pub async fn park_input_with_message(
&self,
requests: InputRequests,
message: impl Into<String>,
) -> Result<PendingInput> {
self.park_input_inner(requests, Some(message.into())).await
}
async fn park_input_inner(
&self,
requests: InputRequests,
message: Option<String>,
) -> Result<PendingInput> {
let live = self.live.as_ref().ok_or_else(|| {
crate::error::Error::Tool(crate::error::ToolError::new(
"require_input needs a live task handler; a replay handler returns RequestOutcome::InputRequired instead",
))
})?;
if requests.is_empty() {
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::ParkInput,
&self.task_id,
"require_input needs at least one request, or the task would wait for something that can never arrive",
),
));
}
let asked: Vec<String> = requests.keys().cloned().collect();
if live.cancelled.is_cancelled() {
return Err(crate::error::Error::TaskCancelled);
}
let accepted = live
.store
.require_input(&self.task_id, requests, message.as_deref())
.await
.map_err(|error| {
crate::error::Error::JsonRpc(live.error_policy.map_store_error(
crate::router::TaskOperation::ParkInput,
&self.task_id,
error,
))
})?;
if !accepted {
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::ParkInput,
&self.task_id,
"the task is already terminal, so it cannot ask for input",
),
));
}
Ok(PendingInput {
live: live.clone(),
task_id: self.task_id.clone(),
asked,
})
}
pub async fn working(&self, message: impl Into<String>) -> Result<()> {
let live = self.live.as_ref().ok_or_else(|| {
crate::error::Error::Tool(crate::error::ToolError::new(
"working needs a live task handler",
))
})?;
let updated = live
.store
.set_status(&self.task_id, TaskStatus::Working, Some(&message.into()))
.await
.map_err(|error| {
crate::error::Error::JsonRpc(live.error_policy.map_store_error(
crate::router::TaskOperation::Execute,
&self.task_id,
error,
))
})?;
if !updated {
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::Execute,
&self.task_id,
"the task is already terminal, so its status cannot be updated",
),
));
}
Ok(())
}
pub fn is_cancelled(&self) -> bool {
self.live
.as_ref()
.is_some_and(|live| live.cancelled.is_cancelled())
}
pub async fn cancelled(&self) {
match self.live.as_ref() {
Some(live) => live.cancelled.cancelled().await,
None => std::future::pending().await,
}
}
}
impl Clone for TaskContext {
fn clone(&self) -> Self {
Self {
task_id: self.task_id.clone(),
live: self.live.clone(),
}
}
}
#[must_use = "the task is parked in `input_required` until this is awaited; \
dropping it leaves the task parked with nothing waiting to \
resume it"]
pub struct PendingInput {
live: Arc<LiveTask>,
task_id: String,
asked: Vec<String>,
}
impl std::fmt::Debug for PendingInput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PendingInput")
.field("task_id", &self.task_id)
.field("asked", &self.asked)
.finish_non_exhaustive()
}
}
impl PendingInput {
pub fn task_id(&self) -> &str {
&self.task_id
}
pub fn asked(&self) -> &[String] {
&self.asked
}
pub async fn wait(self) -> Result<InputResponses> {
let live = &self.live;
if live.cancelled.is_cancelled() {
return Err(crate::error::Error::TaskCancelled);
}
loop {
let woken = live.input_ready.notified();
let outstanding = live
.store
.outstanding_input_requests(&self.task_id)
.await
.map_err(|error| {
crate::error::Error::JsonRpc(live.error_policy.map_store_error(
crate::router::TaskOperation::Execute,
&self.task_id,
error,
))
})?;
let Some(outstanding) = outstanding else {
if live.cancelled.is_cancelled() {
return Err(crate::error::Error::TaskCancelled);
}
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::Execute,
&self.task_id,
"the task disappeared while waiting for input",
),
));
};
if !outstanding.keys().any(|key| self.asked.contains(key)) {
break;
}
tokio::select! {
_ = woken => {}
_ = live.cancelled.cancelled() => {
return Err(crate::error::Error::TaskCancelled);
}
}
}
let all = live
.store
.input_responses(&self.task_id)
.await
.map_err(|error| {
crate::error::Error::JsonRpc(live.error_policy.map_store_error(
crate::router::TaskOperation::Execute,
&self.task_id,
error,
))
})?;
let Some(all) = all else {
if live.cancelled.is_cancelled() {
return Err(crate::error::Error::TaskCancelled);
}
return Err(crate::error::Error::JsonRpc(
live.error_policy.map_internal_error(
crate::router::TaskOperation::Execute,
&self.task_id,
"the task disappeared before its input responses could be read",
),
));
};
Ok(all
.into_iter()
.filter(|(key, _)| self.asked.contains(key))
.collect())
}
}
#[derive(Debug, Clone, Default)]
pub struct TaskPreparation {
pub(crate) meta: Option<Map<String, Value>>,
pub(crate) extensions: Extensions,
}
impl TaskPreparation {
pub fn new() -> Self {
Self::default()
}
pub fn with_meta(mut self, meta: Map<String, Value>) -> Self {
self.meta = Some(meta);
self
}
pub fn with_extension<T: Send + Sync + 'static>(mut self, value: T) -> Self {
self.extensions.insert(value);
self
}
}
pub(crate) trait TaskPreparer: Send + Sync {
fn prepare(
&self,
context: TaskContext,
arguments: Value,
) -> BoxFuture<'_, Result<TaskPreparation>>;
}
impl<F, Fut> TaskPreparer for F
where
F: Fn(TaskContext, Value) -> Fut + Send + Sync,
Fut: Future<Output = Result<TaskPreparation>> + Send + 'static,
{
fn prepare(
&self,
context: TaskContext,
arguments: Value,
) -> BoxFuture<'_, Result<TaskPreparation>> {
Box::pin((self)(context, arguments))
}
}
struct TypedTaskPreparer<I, F> {
prepare: F,
_phantom: std::marker::PhantomData<I>,
}
impl<I, F, Fut> TaskPreparer for TypedTaskPreparer<I, F>
where
I: DeserializeOwned + Send + Sync + 'static,
F: Fn(TaskContext, I) -> Fut + Send + Sync,
Fut: Future<Output = Result<TaskPreparation>> + Send + 'static,
{
fn prepare(
&self,
context: TaskContext,
arguments: Value,
) -> BoxFuture<'_, Result<TaskPreparation>> {
let input = serde_json::from_value(arguments)
.map_err(|error| Error::invalid_params(format!("Invalid input: {error}")));
match input {
Ok(input) => Box::pin((self.prepare)(context, input)),
Err(error) => Box::pin(async move { Err(error) }),
}
}
}
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)
}
}
#[cfg(test)]
mod tests;