use std::sync::Arc;
use serde_json::{json, Value};
use crate::ToolKind;
use super::client::{McpClient, McpResource, McpToolDef};
use crate::openai_compatible::tools::{Tool, ToolCtx, ToolOutcome};
pub(crate) struct McpTool {
client: Arc<McpClient>,
id: String,
remote_name: String,
description: String,
schema: Value,
}
impl McpTool {
pub(crate) fn new(client: Arc<McpClient>, server: &str, def: McpToolDef) -> Self {
Self {
client,
id: format!("{server}_{}", def.name),
remote_name: def.name,
description: def.description,
schema: def.input_schema,
}
}
}
impl Tool for McpTool {
fn id(&self) -> &str {
&self.id
}
fn description(&self) -> &str {
&self.description
}
fn parameters(&self) -> Value {
self.schema.clone()
}
fn kind(&self) -> ToolKind {
ToolKind::Other
}
fn mutating(&self) -> bool {
true
}
fn execute(&self, args: &Value, _ctx: &ToolCtx) -> ToolOutcome {
let arguments = if args.is_object() { args.clone() } else { Value::Object(Default::default()) };
match self.client.call(&self.remote_name, &arguments) {
Ok(text) => ToolOutcome::ok(if text.trim().is_empty() { "(no content)".to_owned() } else { text }),
Err(e) => ToolOutcome::err(e),
}
}
}
pub(crate) struct McpResourceTool {
client: Arc<McpClient>,
id: String,
description: String,
}
impl McpResourceTool {
pub(crate) fn new(client: Arc<McpClient>, server: &str, resources: &[McpResource]) -> Self {
let mut description =
format!("Read a resource exposed by the `{server}` MCP server, by URI. Available resources:\n");
for r in resources {
let name = if r.name.is_empty() { String::new() } else { format!(" ({})", r.name) };
let desc = if r.description.is_empty() { String::new() } else { format!(" — {}", r.description) };
description.push_str(&format!("- `{}`{name}{desc}\n", r.uri));
}
Self { client, id: format!("{server}_read_resource"), description }
}
}
impl Tool for McpResourceTool {
fn id(&self) -> &str {
&self.id
}
fn description(&self) -> &str {
&self.description
}
fn parameters(&self) -> Value {
json!({
"type": "object",
"properties": { "uri": { "type": "string", "description": "The resource URI to read (one listed in this tool's description)." } },
"required": ["uri"]
})
}
fn kind(&self) -> ToolKind {
ToolKind::Read
}
fn mutating(&self) -> bool {
false }
fn execute(&self, args: &Value, _ctx: &ToolCtx) -> ToolOutcome {
let Some(uri) = args.get("uri").and_then(Value::as_str) else {
return ToolOutcome::err("read_resource: a `uri` string is required");
};
match self.client.read_resource(uri) {
Ok(text) => ToolOutcome::ok(if text.trim().is_empty() { "(empty resource)".to_owned() } else { text }),
Err(e) => ToolOutcome::err(e),
}
}
}