capo-agent 0.2.0

Coding-agent library built on motosan-agent-loop. Composable, embeddable.
Documentation
#![allow(clippy::expect_used, clippy::unwrap_used)]

use std::collections::HashMap;
use std::sync::Arc;

use capo_agent::mcp::{connect_all, disconnect_all, McpConfig, McpServerConfig};
use motosan_agent_loop::mcp::McpToolAdapter;
use motosan_agent_tool::ToolContext;
use tempfile::tempdir;

fn fixture_binary_path() -> std::path::PathBuf {
    // Cargo places workspace bins in $CARGO_TARGET_DIR/debug.
    // `CARGO_TARGET_DIR` is set in CI / tests via env; otherwise fall back.
    let target = std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "target".into());
    std::path::PathBuf::from(target)
        .join("debug")
        .join("fake-mcp-stdio")
}

#[tokio::test]
async fn connect_list_call_disconnect_roundtrip() {
    let bin = fixture_binary_path();
    if !bin.exists() {
        eprintln!(
            "Skipping: fake-mcp-stdio not built at {}. Run `cargo build -p fake-mcp-stdio` first.",
            bin.display()
        );
        return;
    }

    let mut servers = HashMap::new();
    servers.insert(
        "fake".into(),
        McpServerConfig::Stdio {
            command: bin.display().to_string(),
            args: vec![],
            env: HashMap::new(),
            startup_timeout_ms: 5000,
            enabled: true,
        },
    );
    let cfg = McpConfig { servers };

    let log_dir = tempdir().unwrap();
    let started = connect_all(&cfg, log_dir.path()).await;
    assert_eq!(started.len(), 1, "fake server should have connected");

    let server = Arc::clone(&started[0].server);
    let tools = McpToolAdapter::from_server(server)
        .await
        .expect("list_tools");
    let names: Vec<String> = tools.iter().map(|t| t.def().name).collect();
    assert!(names.contains(&"fake__echo".to_string()), "got {names:?}");

    let echo = tools
        .iter()
        .find(|t| t.def().name == "fake__echo")
        .expect("fake__echo tool");
    let ctx = ToolContext::new("test", "capo");
    let result = echo
        .call(serde_json::json!({"text": "hello mcp"}), &ctx)
        .await;
    assert!(!result.is_error, "{result:?}");
    assert_eq!(result.as_text(), Some("hello mcp"));

    disconnect_all(&started).await;

    let log_file = log_dir.path().join("fake.stderr.log");
    let log = std::fs::read_to_string(&log_file)
        .unwrap_or_else(|e| panic!("read {} failed: {e}", log_file.display()));
    assert!(
        log.contains("fake-mcp-stdio ready"),
        "stderr log missing fixture line: {log:?}"
    );
}