use serde::{Deserialize, Serialize};
use serde_json::Value;
use ai_agents_tools::mcp::wrapper::MCPWrapperConfig;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ToolEntry {
Simple(String),
Structured(StructuredToolEntry),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StructuredToolEntry {
pub name: String,
#[serde(rename = "type", default)]
pub tool_type: Option<String>,
#[serde(flatten)]
pub extra: Value,
}
impl ToolEntry {
pub fn name(&self) -> &str {
match self {
ToolEntry::Simple(name) => name,
ToolEntry::Structured(s) => &s.name,
}
}
pub fn is_mcp(&self) -> bool {
match self {
ToolEntry::Simple(_) => false,
ToolEntry::Structured(s) => s.tool_type.as_deref() == Some("mcp"),
}
}
pub fn to_mcp_config(&self) -> Option<MCPWrapperConfig> {
if !self.is_mcp() {
return None;
}
match self {
ToolEntry::Structured(s) => {
let value = serde_json::to_value(s).ok()?;
serde_json::from_value(value).ok()
}
_ => None,
}
}
}
pub type ToolConfig = ToolEntry;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tool_entry_plain_string() {
let yaml = "datetime";
let entry: ToolEntry = serde_yaml::from_str(yaml).unwrap();
assert_eq!(entry.name(), "datetime");
assert!(!entry.is_mcp());
}
#[test]
fn test_tool_entry_structured_builtin() {
let yaml = "name: http";
let entry: ToolEntry = serde_yaml::from_str(yaml).unwrap();
assert_eq!(entry.name(), "http");
assert!(!entry.is_mcp());
}
#[test]
fn test_tool_entry_mcp() {
let yaml = r#"
name: github
type: mcp
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "test"
"#;
let entry: ToolEntry = serde_yaml::from_str(yaml).unwrap();
assert_eq!(entry.name(), "github");
assert!(entry.is_mcp());
let config = entry.to_mcp_config().unwrap();
assert_eq!(config.name, "github");
}
#[test]
fn test_tool_entry_mixed_list() {
let yaml = r#"
- datetime
- name: http
- name: github
type: mcp
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "test"
"#;
let entries: Vec<ToolEntry> = serde_yaml::from_str(yaml).unwrap();
assert_eq!(entries.len(), 3);
assert_eq!(entries[0].name(), "datetime");
assert!(!entries[0].is_mcp());
assert_eq!(entries[1].name(), "http");
assert!(!entries[1].is_mcp());
assert_eq!(entries[2].name(), "github");
assert!(entries[2].is_mcp());
}
#[test]
fn test_tool_config_backward_compat() {
let config = ToolConfig::Simple("echo".to_string());
assert_eq!(config.name(), "echo");
}
#[test]
fn test_tool_entry_mcp_with_views() {
let yaml = r#"
name: github
type: mcp
transport: stdio
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "test"
views:
github_issues:
functions: [create_issue, list_issues]
github_code:
functions: [search_code]
description: "Code search"
"#;
let entry: ToolEntry = serde_yaml::from_str(yaml).unwrap();
assert_eq!(entry.name(), "github");
assert!(entry.is_mcp());
let config = entry.to_mcp_config().unwrap();
assert_eq!(config.views.len(), 2);
assert_eq!(
config.views["github_issues"].functions,
vec!["create_issue", "list_issues"]
);
assert_eq!(
config.views["github_code"].description.as_deref(),
Some("Code search")
);
}
#[test]
fn test_tool_entry_name_method() {
let simple = ToolEntry::Simple("calculator".to_string());
assert_eq!(simple.name(), "calculator");
let structured = ToolEntry::Structured(StructuredToolEntry {
name: "custom_tool".to_string(),
tool_type: None,
extra: serde_json::json!({}),
});
assert_eq!(structured.name(), "custom_tool");
}
}