use jamjet_a2a_types::*;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolDefinition {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub input_schema: Value,
}
pub fn agent_card_to_mcp_tools(card: &AgentCard) -> Vec<McpToolDefinition> {
card.skills
.iter()
.map(|skill| {
let input_schema = serde_json::json!({
"type": "object",
"properties": {
"message": {
"type": "string",
"description": format!("Input for {}", skill.name),
}
},
"required": ["message"],
});
McpToolDefinition {
name: skill.name.clone(),
description: if skill.description.is_empty() {
None
} else {
Some(skill.description.clone())
},
input_schema,
}
})
.collect()
}
pub fn mcp_tools_to_agent_card(tools: &[McpToolDefinition], name: &str) -> AgentCard {
let skills: Vec<AgentSkill> = tools
.iter()
.enumerate()
.map(|(i, tool)| AgentSkill {
id: format!("mcp-tool-{i}"),
name: tool.name.clone(),
description: tool.description.clone().unwrap_or_default(),
tags: vec!["mcp".into()],
..Default::default()
})
.collect();
AgentCard {
name: name.into(),
description: format!("Agent bridged from {n} MCP tool(s)", n = tools.len()),
version: "1.0".into(),
supported_interfaces: vec![],
capabilities: AgentCapabilities {
streaming: None,
push_notifications: None,
extensions: vec![],
extended_agent_card: None,
},
default_input_modes: vec!["text/plain".into()],
default_output_modes: vec!["text/plain".into()],
skills,
provider: None,
security_schemes: HashMap::new(),
security_requirements: vec![],
signatures: vec![],
icon_url: None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_card_with_skills(skill_names: &[&str]) -> AgentCard {
let skills = skill_names
.iter()
.enumerate()
.map(|(i, name)| AgentSkill {
id: format!("s{i}"),
name: name.to_string(),
description: format!("{name} skill"),
..Default::default()
})
.collect();
AgentCard {
name: "test-agent".into(),
description: "Test agent".into(),
version: "1.0".into(),
supported_interfaces: vec![],
capabilities: AgentCapabilities {
streaming: None,
push_notifications: None,
extensions: vec![],
extended_agent_card: None,
},
default_input_modes: vec!["text/plain".into()],
default_output_modes: vec!["text/plain".into()],
skills,
provider: None,
security_schemes: HashMap::new(),
security_requirements: vec![],
signatures: vec![],
icon_url: None,
}
}
#[test]
fn card_with_two_skills_produces_two_tools() {
let card = make_card_with_skills(&["summarize", "translate"]);
let tools = agent_card_to_mcp_tools(&card);
assert_eq!(tools.len(), 2);
assert_eq!(tools[0].name, "summarize");
assert_eq!(tools[1].name, "translate");
assert!(tools[0].description.is_some());
assert!(tools[1].description.is_some());
}
#[test]
fn round_trip_card_to_tools_to_card() {
let original = make_card_with_skills(&["search", "classify", "generate"]);
let tools = agent_card_to_mcp_tools(&original);
let rebuilt = mcp_tools_to_agent_card(&tools, "rebuilt-agent");
let original_names: Vec<&str> = original.skills.iter().map(|s| s.name.as_str()).collect();
let rebuilt_names: Vec<&str> = rebuilt.skills.iter().map(|s| s.name.as_str()).collect();
assert_eq!(original_names, rebuilt_names);
}
#[test]
fn empty_skills_produces_empty_tools() {
let card = make_card_with_skills(&[]);
let tools = agent_card_to_mcp_tools(&card);
assert!(tools.is_empty());
}
}