use serde::{Deserialize, Serialize};
use crate::adapters::ToolAdapter;
use crate::ToolMeta;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolSpec {
pub name: String,
pub description: String,
pub input_schema: serde_json::Value,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub examples: Vec<McpExample>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpExample {
pub name: String,
pub input: serde_json::Value,
}
#[derive(Debug, Clone, Deserialize)]
pub struct McpCallRequest {
pub name: String,
pub arguments: serde_json::Value,
}
#[derive(Debug, Clone, Serialize)]
pub struct McpCallResponse {
pub content: Vec<McpContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
}
fn is_false() -> bool {
false
}
#[derive(Debug, Clone, Serialize)]
pub struct McpContent {
#[serde(rename = "type")]
pub content_type: String,
pub text: String,
}
pub struct McpAdapter;
impl ToolAdapter for McpAdapter {
type ToolSpec = McpToolSpec;
type CallRequest = McpCallRequest;
type CallResponse = McpCallResponse;
fn to_spec(meta: &ToolMeta) -> McpToolSpec {
let examples: Vec<McpExample> = (meta.examples)()
.iter()
.map(|(name, json_str)| {
McpExample {
name: name.to_string(),
input: serde_json::from_str(json_str).unwrap_or(serde_json::json!({})),
}
})
.collect();
McpToolSpec {
name: meta.name.to_string(),
description: meta.description.to_string(),
input_schema: (meta.schema)().clone(),
examples,
}
}
fn from_request(req: McpCallRequest) -> Result<(String, serde_json::Value), crate::ToolError> {
Ok((req.name, req.arguments))
}
fn to_response(output: serde_json::Value) -> McpCallResponse {
McpCallResponse {
content: vec![McpContent {
content_type: "text".to_string(),
text: serde_json::to_string(&output).unwrap_or_else(|_| output.to_string()),
}],
is_error: None,
}
}
}
impl McpAdapter {
pub fn error_response(error_message: String) -> McpCallResponse {
McpCallResponse {
content: vec![McpContent {
content_type: "text".to_string(),
text: error_message,
}],
is_error: Some(true),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Tool, ToolContext, ToolError};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
#[derive(Deserialize, JsonSchema)]
struct TestParams {
value: i32,
}
#[derive(Serialize, JsonSchema)]
struct TestOutput {
result: i32,
}
struct TestTool;
impl Tool for TestTool {
type Params = TestParams;
type Output = TestOutput;
const NAME: &'static str = "test";
const DESCRIPTION: &'static str = "Test tool";
async fn call(
_ctx: Arc<ToolContext>,
params: Self::Params,
) -> Result<Self::Output, ToolError> {
Ok(TestOutput {
result: params.value * 2,
})
}
}
inventory::submit! {
crate::ToolMeta {
name: TestTool::NAME,
description: TestTool::DESCRIPTION,
call: |ctx, params| {
Box::pin(async move {
let params: TestParams = serde_json::from_value(params)?;
let result = <TestTool as Tool>::call(ctx, params).await?;
Ok(serde_json::to_value(result)?)
})
},
schema: || <TestTool as Tool>::schema(),
examples: || <TestTool as Tool>::EXAMPLES,
}
}
#[test]
fn test_to_spec() {
let meta = crate::find_tool("test").unwrap();
let spec = McpAdapter::to_spec(meta);
assert_eq!(spec.name, "test");
assert_eq!(spec.description, "Test tool");
assert!(spec.input_schema.is_object());
}
#[test]
fn test_from_request() {
let req = McpCallRequest {
name: "test".to_string(),
arguments: serde_json::json!({"value": 5}),
};
let (name, params) = McpAdapter::from_request(req).unwrap();
assert_eq!(name, "test");
assert_eq!(params["value"], 5);
}
#[test]
fn test_to_response() {
let output = serde_json::json!({"result": 10});
let response = McpAdapter::to_response(output);
assert_eq!(response.content.len(), 1);
assert_eq!(response.content[0].content_type, "text");
assert_eq!(response.is_error, None);
}
#[test]
fn test_error_response() {
let response = McpAdapter::error_response("Test error".to_string());
assert_eq!(response.is_error, Some(true));
assert_eq!(response.content[0].text, "Test error");
}
}