use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MCPError {
#[error("Formato de comando inválido (esperado 'agente:acao')")]
InvalidCommandFormat,
#[error("Agente '{0}' não foi encontrado no registro")]
AgentNotRegistered(String),
#[error("Erro interno do agente: {0}")]
InternalAgentError(String),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct MCPMessage {
pub magic: String,
pub version: u8,
pub command: String,
pub payload: Value,
}
impl MCPMessage {
pub fn new(command: &str, payload: Value) -> Self {
MCPMessage {
magic: "MCP0".to_string(),
version: 1,
command: command.to_string(),
payload,
}
}
}
#[async_trait]
pub trait AIAgent: Send + Sync {
fn name(&self) -> &str;
async fn process_request(&self, message: MCPMessage) -> Result<MCPMessage, MCPError>;
}
pub struct AgentRegistry {
agents: HashMap<String, Box<dyn AIAgent>>,
}
impl AgentRegistry {
pub fn new() -> Self {
AgentRegistry {
agents: HashMap::new(),
}
}
pub fn register_agent(&mut self, agent: Box<dyn AIAgent>) {
self.agents.insert(agent.name().to_string(), agent);
}
pub async fn process(&self, message: MCPMessage) -> Result<MCPMessage, MCPError> {
let parts: Vec<&str> = message.command.splitn(2, ':').collect();
if parts.len() != 2 {
return Err(MCPError::InvalidCommandFormat);
}
let agent_key = parts[0];
if let Some(agent) = self.agents.get(agent_key) {
agent.process_request(message).await
} else {
Err(MCPError::AgentNotRegistered(agent_key.to_string()))
}
}
}
pub struct DummyAgent {
pub api_key: String,
}
#[async_trait]
impl AIAgent for DummyAgent {
fn name(&self) -> &str {
"dummy"
}
async fn process_request(&self, message: MCPMessage) -> Result<MCPMessage, MCPError> {
Ok(MCPMessage::new("dummy_response", message.payload))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[tokio::test]
async fn test_mcpmessage_new() {
let msg = MCPMessage::new("test:command", json!({"key": "value"}));
assert_eq!(msg.magic, "MCP0");
assert_eq!(msg.version, 1);
assert_eq!(msg.command, "test:command");
assert_eq!(msg.payload, json!({"key": "value"}));
}
#[tokio::test]
async fn test_dummy_agent() {
let agent = DummyAgent {
api_key: "test_key".to_string(),
};
let msg = MCPMessage::new("dummy:test", json!({"echo": "this"}));
let result = agent.process_request(msg).await.unwrap();
assert_eq!(result.command, "dummy_response");
assert_eq!(result.payload, json!({"echo": "this"}));
}
#[tokio::test]
async fn test_registry_routing() {
let mut registry = AgentRegistry::new();
registry.register_agent(Box::new(DummyAgent {
api_key: "test_key".to_string(),
}));
let msg = MCPMessage::new("dummy:action", json!({"test": true}));
let result = registry.process(msg).await.unwrap();
assert_eq!(result.command, "dummy_response");
let msg2 = MCPMessage::new("unknown:action", json!({}));
let err = registry.process(msg2).await.unwrap_err();
assert!(matches!(err, MCPError::AgentNotRegistered(s) if s == "unknown"));
let msg3 = MCPMessage::new("invalid-format", json!({}));
let err = registry.process(msg3).await.unwrap_err();
assert!(matches!(err, MCPError::InvalidCommandFormat));
}
}