use super::{Command, AgentsMdDocument};
use serde_json::{json, Value as JsonValue};
pub struct McpAgentsMdBridge {
config: BridgeConfig,
tool_registry: Vec<McpTool>,
}
#[derive(Debug, Clone)]
pub struct BridgeConfig {
pub bidirectional: bool,
pub auto_discover: bool,
pub quality_level: QualityLevel,
}
impl Default for BridgeConfig {
fn default() -> Self {
Self {
bidirectional: true,
auto_discover: true,
quality_level: QualityLevel::Standard,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QualityLevel {
None,
Basic,
Standard,
Strict,
Extreme,
}
#[derive(Debug, Clone)]
pub struct McpTool {
pub name: String,
pub description: String,
pub input_schema: JsonValue,
pub output_schema: JsonValue,
pub handler: ToolHandler,
}
#[derive(Debug, Clone)]
pub enum ToolHandler {
Command(Command),
Function(String),
External(String),
}
#[derive(Debug, Clone)]
pub enum Request {
AgentsMd(AgentsMdRequest),
Mcp(McpRequest),
}
#[derive(Debug, Clone)]
pub struct AgentsMdRequest {
pub request_type: String,
pub params: JsonValue,
}
#[derive(Debug, Clone)]
pub struct McpRequest {
pub method: String,
pub params: JsonValue,
}
#[derive(Debug, Clone)]
pub struct TranslatedRequest {
pub original: Request,
pub translated: Request,
pub metadata: TranslationMetadata,
}
#[derive(Debug, Clone)]
pub struct TranslationMetadata {
pub timestamp: std::time::SystemTime,
pub quality_checks: Vec<String>,
pub warnings: Vec<String>,
}
#[derive(Debug, Clone)]
pub enum Response {
AgentsMd(AgentsMdResponse),
Mcp(McpResponse),
}
#[derive(Debug, Clone)]
pub struct AgentsMdResponse {
pub success: bool,
pub result: JsonValue,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct McpResponse {
pub result: JsonValue,
pub error: Option<JsonValue>,
}
#[derive(Debug, Clone)]
pub struct UnifiedResponse {
pub original: Response,
pub unified: JsonValue,
pub quality_report: Option<QualityReport>,
}
#[derive(Debug, Clone)]
pub struct QualityReport {
pub score: f64,
pub issues: Vec<String>,
pub suggestions: Vec<String>,
}
impl Default for McpAgentsMdBridge {
fn default() -> Self {
Self::new()
}
}
impl McpAgentsMdBridge {
#[must_use]
pub fn new() -> Self {
Self {
config: BridgeConfig::default(),
tool_registry: Vec::new(),
}
}
#[must_use]
pub fn with_config(config: BridgeConfig) -> Self {
Self {
config,
tool_registry: Vec::new(),
}
}
#[must_use]
pub fn agents_to_mcp(&self, doc: &AgentsMdDocument) -> Vec<McpTool> {
let mut tools = Vec::new();
for cmd in &doc.commands {
tools.push(self.command_to_tool(cmd));
}
if self.config.quality_level != QualityLevel::None {
tools.push(self.create_quality_tool());
}
tools
}
#[must_use]
pub fn mcp_to_agents(&self, tools: &[McpTool]) -> String {
let mut output = String::new();
output.push_str("# AGENTS.md\n\n");
output.push_str("## Available Tools\n\n");
for tool in tools {
output.push_str(&format!("### {}\n", tool.name));
output.push_str(&format!("{}\n\n", tool.description));
if let ToolHandler::Command(ref cmd) = tool.handler {
output.push_str("```bash\n");
output.push_str(&format!("{}\n", cmd.command));
output.push_str("```\n\n");
}
}
output
}
#[must_use]
pub fn translate_request(&self, req: Request) -> TranslatedRequest {
let metadata = TranslationMetadata {
timestamp: std::time::SystemTime::now(),
quality_checks: vec![],
warnings: vec![],
};
let translated = match req {
Request::AgentsMd(ref agents_req) => {
Request::Mcp(self.agents_request_to_mcp(agents_req))
}
Request::Mcp(ref mcp_req) => Request::AgentsMd(self.mcp_request_to_agents(mcp_req)),
};
TranslatedRequest {
original: req,
translated,
metadata,
}
}
#[must_use]
pub fn unify_response(&self, resp: Response) -> UnifiedResponse {
let unified = match resp {
Response::AgentsMd(ref agents_resp) => self.agents_response_to_unified(agents_resp),
Response::Mcp(ref mcp_resp) => self.mcp_response_to_unified(mcp_resp),
};
let quality_report = if self.config.quality_level == QualityLevel::None {
None
} else {
Some(self.check_response_quality(&unified))
};
UnifiedResponse {
original: resp,
unified,
quality_report,
}
}
fn command_to_tool(&self, cmd: &Command) -> McpTool {
McpTool {
name: cmd.name.clone(),
description: format!("Execute: {}", cmd.command),
input_schema: json!({
"type": "object",
"properties": {
"args": {
"type": "array",
"items": {"type": "string"}
}
}
}),
output_schema: json!({
"type": "object",
"properties": {
"stdout": {"type": "string"},
"stderr": {"type": "string"},
"exit_code": {"type": "integer"}
}
}),
handler: ToolHandler::Command(cmd.clone()),
}
}
fn create_quality_tool(&self) -> McpTool {
McpTool {
name: "quality_gate".to_string(),
description: "Run PMAT quality gates".to_string(),
input_schema: json!({
"type": "object",
"properties": {
"file": {"type": "string"},
"level": {"type": "string"}
}
}),
output_schema: json!({
"type": "object",
"properties": {
"passed": {"type": "boolean"},
"score": {"type": "number"},
"issues": {
"type": "array",
"items": {"type": "string"}
}
}
}),
handler: ToolHandler::Function("quality_gate".to_string()),
}
}
fn agents_request_to_mcp(&self, req: &AgentsMdRequest) -> McpRequest {
McpRequest {
method: req.request_type.clone(),
params: req.params.clone(),
}
}
fn mcp_request_to_agents(&self, req: &McpRequest) -> AgentsMdRequest {
AgentsMdRequest {
request_type: req.method.clone(),
params: req.params.clone(),
}
}
fn agents_response_to_unified(&self, resp: &AgentsMdResponse) -> JsonValue {
json!({
"success": resp.success,
"result": resp.result,
"error": resp.error,
})
}
fn mcp_response_to_unified(&self, resp: &McpResponse) -> JsonValue {
json!({
"success": resp.error.is_none(),
"result": resp.result,
"error": resp.error,
})
}
fn check_response_quality(&self, response: &JsonValue) -> QualityReport {
let mut issues = Vec::new();
let mut suggestions = Vec::new();
let mut score: f64 = 100.0;
if let Some(error) = response.get("error") {
if !error.is_null() {
issues.push("Response contains error".to_string());
score -= 20.0;
}
}
if let Some(result) = response.get("result") {
if result.is_null() || (result.is_string() && result.as_str() == Some("")) {
issues.push("Empty result".to_string());
suggestions.push("Provide meaningful output".to_string());
score -= 10.0;
}
}
QualityReport {
score: score.max(0.0),
issues,
suggestions,
}
}
pub fn register_tool(&mut self, tool: McpTool) {
self.tool_registry.push(tool);
}
#[must_use]
pub fn get_tools(&self) -> &[McpTool] {
&self.tool_registry
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::agents_md::DocumentMetadata;
use std::path::PathBuf;
#[test]
fn test_bridge_creation() {
let bridge = McpAgentsMdBridge::new();
assert!(bridge.config.bidirectional);
assert!(bridge.config.auto_discover);
assert_eq!(bridge.config.quality_level, QualityLevel::Standard);
}
#[test]
fn test_agents_to_mcp_conversion() {
let bridge = McpAgentsMdBridge::new();
let doc = AgentsMdDocument {
metadata: DocumentMetadata {
path: PathBuf::from("AGENTS.md"),
modified: std::time::SystemTime::now(),
version: None,
project: None,
},
sections: vec![],
commands: vec![Command {
name: "Build".to_string(),
command: "cargo build".to_string(),
working_dir: None,
env: vec![],
timeout: Some(60),
safe: true,
}],
guidelines: vec![],
quality_rules: None,
};
let tools = bridge.agents_to_mcp(&doc);
assert_eq!(tools.len(), 2); assert_eq!(tools[0].name, "Build");
assert_eq!(tools[1].name, "quality_gate");
}
#[test]
fn test_mcp_to_agents_conversion() {
let bridge = McpAgentsMdBridge::new();
let tools = vec![McpTool {
name: "test_tool".to_string(),
description: "Test tool".to_string(),
input_schema: json!({}),
output_schema: json!({}),
handler: ToolHandler::Function("test".to_string()),
}];
let agents_md = bridge.mcp_to_agents(&tools);
assert!(agents_md.contains("# AGENTS.md"));
assert!(agents_md.contains("test_tool"));
assert!(agents_md.contains("Test tool"));
}
#[test]
fn test_request_translation() {
let bridge = McpAgentsMdBridge::new();
let agents_req = AgentsMdRequest {
request_type: "execute".to_string(),
params: json!({"command": "test"}),
};
let translated = bridge.translate_request(Request::AgentsMd(agents_req));
if let Request::Mcp(mcp_req) = translated.translated {
assert_eq!(mcp_req.method, "execute");
assert_eq!(mcp_req.params, json!({"command": "test"}));
} else {
panic!("Expected MCP request");
}
}
#[test]
fn test_response_unification() {
let bridge = McpAgentsMdBridge::new();
let agents_resp = AgentsMdResponse {
success: true,
result: json!({"output": "test"}),
error: None,
};
let unified = bridge.unify_response(Response::AgentsMd(agents_resp));
assert!(unified.quality_report.is_some());
let report = unified.quality_report.unwrap();
assert_eq!(report.score, 100.0);
assert!(report.issues.is_empty());
}
#[test]
fn test_quality_checking() {
let bridge = McpAgentsMdBridge::new();
let response = json!({
"success": false,
"result": "",
"error": "Test error"
});
let report = bridge.check_response_quality(&response);
assert!(report.score < 100.0);
assert!(!report.issues.is_empty());
assert!(report
.issues
.contains(&"Response contains error".to_string()));
}
#[test]
fn test_tool_registration() {
let mut bridge = McpAgentsMdBridge::new();
let tool = McpTool {
name: "custom_tool".to_string(),
description: "Custom tool".to_string(),
input_schema: json!({}),
output_schema: json!({}),
handler: ToolHandler::External("external".to_string()),
};
bridge.register_tool(tool);
assert_eq!(bridge.get_tools().len(), 1);
assert_eq!(bridge.get_tools()[0].name, "custom_tool");
}
}