rust-sdk/examples/simple-chat-client/src/tool.rs
Dale Seo 9e881a645b
Implement SEP-1319: Decouple Request Payload from RPC Methods (#617)
* feat: implement SEP-1319 Decouple Request Payload from RPC Methods

* test: update tests

* fix: update handler trait methods to use new types

* fix: update examples

* fix: correct deprecation version

* fix: update wrapper macros to use new *Params type names
2026-01-16 12:16:52 -05:00

137 lines
3.5 KiB
Rust

use std::{collections::HashMap, sync::Arc};
use anyhow::Result;
use async_trait::async_trait;
use rmcp::{
RoleClient,
model::{CallToolRequestParams, CallToolResult, Tool as McpTool},
service::{RunningService, ServerSink},
};
use serde_json::Value;
use crate::{
error::McpError,
model::{Content, ToolResult},
};
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> String;
fn description(&self) -> String;
fn parameters(&self) -> Value;
async fn call(&self, args: Value) -> Result<CallToolResult>;
}
pub struct McpToolAdapter {
tool: McpTool,
server: ServerSink,
}
impl McpToolAdapter {
pub fn new(tool: McpTool, server: ServerSink) -> Self {
Self { tool, server }
}
}
#[async_trait]
impl Tool for McpToolAdapter {
fn name(&self) -> String {
self.tool.name.clone().to_string()
}
fn description(&self) -> String {
self.tool
.description
.clone()
.unwrap_or_default()
.to_string()
}
fn parameters(&self) -> Value {
serde_json::to_value(&self.tool.input_schema).unwrap_or(serde_json::json!({}))
}
async fn call(&self, args: Value) -> Result<CallToolResult> {
let arguments = match args {
Value::Object(map) => Some(map),
_ => None,
};
println!("arguments: {:?}", arguments);
let call_result = self
.server
.call_tool(CallToolRequestParams {
meta: None,
name: self.tool.name.clone(),
arguments,
task: None,
})
.await?;
Ok(call_result)
}
}
#[derive(Default)]
pub struct ToolSet {
tools: HashMap<String, Arc<dyn Tool>>,
clients: HashMap<String, RunningService<RoleClient, ()>>,
}
impl ToolSet {
pub fn set_clients(&mut self, clients: HashMap<String, RunningService<RoleClient, ()>>) {
self.clients = clients;
}
pub fn add_tool<T: Tool + 'static>(&mut self, tool: T) {
self.tools.insert(tool.name(), Arc::new(tool));
}
pub fn get_tool(&self, name: &str) -> Option<Arc<dyn Tool>> {
self.tools.get(name).cloned()
}
pub fn tools(&self) -> Vec<Arc<dyn Tool>> {
self.tools.values().cloned().collect()
}
}
pub async fn get_mcp_tools(server: ServerSink) -> Result<Vec<McpToolAdapter>> {
let tools = server.list_all_tools().await?;
Ok(tools
.into_iter()
.map(|tool| McpToolAdapter::new(tool, server.clone()))
.collect())
}
pub trait IntoCallToolResult {
fn into_call_tool_result(self) -> Result<ToolResult, McpError>;
}
impl<T> IntoCallToolResult for Result<T, McpError>
where
T: serde::Serialize,
{
fn into_call_tool_result(self) -> Result<ToolResult, McpError> {
match self {
Ok(response) => {
let content = Content {
content_type: "application/json".to_string(),
body: serde_json::to_string(&response).unwrap_or_default(),
};
Ok(ToolResult {
success: true,
contents: vec![content],
})
}
Err(error) => {
let content = Content {
content_type: "application/json".to_string(),
body: serde_json::to_string(&error).unwrap_or_default(),
};
Ok(ToolResult {
success: false,
contents: vec![content],
})
}
}
}
}