mcp-compressor-core 0.0.2

Internal Rust core for mcp-compressor. Prefer the public mcp-compressor crate.
Documentation
mod common;

use std::process::Command;

use mcp_compressor_core::client_gen::cli::CliGenerator;
use mcp_compressor_core::client_gen::python::PythonGenerator;
use mcp_compressor_core::client_gen::typescript::TypeScriptGenerator;
use mcp_compressor_core::client_gen::{ClientGenerator, GeneratorConfig};
use mcp_compressor_core::proxy::{RunningToolProxy, ToolProxyServer};
use mcp_compressor_core::server::CompressedServer;

async fn real_backend_tools() -> Vec<mcp_compressor_core::compression::engine::Tool> {
    let compressed = CompressedServer::connect_stdio(
        common::max_config(Some("alpha")),
        common::backend("alpha", "alpha_server.py"),
    )
    .await
    .unwrap();
    compressed.backend_tools()
}

async fn running_proxy_config(output_dir: &std::path::Path) -> (GeneratorConfig, RunningToolProxy) {
    let compressed = CompressedServer::connect_stdio(
        common::max_config(Some("alpha")),
        common::backend("alpha", "alpha_server.py"),
    )
    .await
    .unwrap();
    let proxy = ToolProxyServer::start(compressed).await.unwrap();

    let config = GeneratorConfig {
        cli_name: "alpha".to_string(),
        bridge_url: proxy.bridge_url().to_string(),
        token: proxy.token_value().to_string(),
        tools: vec![mcp_compressor_core::compression::engine::Tool::new(
            "echo",
            Some("Echo a message from alpha.".to_string()),
            serde_json::json!({
                "type": "object",
                "properties": { "message": { "type": "string" } },
                "required": ["message"]
            }),
        )],
        session_pid: std::process::id(),
        output_dir: output_dir.to_path_buf(),
    };
    (config, proxy)
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn generated_typescript_module_reports_stopped_proxy_without_fetch_noise() {
    let tempdir = tempfile::tempdir().unwrap();
    let (config, proxy) = running_proxy_config(tempdir.path()).await;
    let mut config = config;
    config.tools = real_backend_tools().await;
    let paths = TypeScriptGenerator.generate(&config).unwrap();
    let module = paths
        .iter()
        .find(|path| path.file_name().unwrap() == "alpha.ts")
        .unwrap();

    drop(proxy);

    let output = Command::new("bun")
        .arg("--eval")
        .arg(format!(
            "import {{ echo }} from {module:?}; await echo('hello')",
            module = module.display().to_string()
        ))
        .output()
        .unwrap();

    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("mcp-compressor proxy is not running"),
        "stderr: {stderr}"
    );
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn generated_cli_script_handles_structured_json_arguments() {
    let tempdir = tempfile::tempdir().unwrap();
    let (config, _proxy) = running_proxy_config(tempdir.path()).await;
    let mut config = config;
    config.tools = real_backend_tools().await;

    CliGenerator.generate(&config).unwrap();
    let script = tempdir.path().join("alpha");

    let output = std::process::Command::new(&script)
        .args([
            "summarize-payload",
            "--items",
            "[\"one\",\"two\"]",
            "--metadata",
            "{\"source\":\"generated-cli\",\"ok\":true}",
        ])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    let stdout = String::from_utf8_lossy(&output.stdout);
    assert!(stdout.contains("item_count"));
    assert!(stdout.contains("generated-cli"));
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn generated_cli_script_invokes_real_proxy_and_backend() {
    let tempdir = tempfile::tempdir().unwrap();
    let (config, _proxy) = running_proxy_config(tempdir.path()).await;
    let mut config = config;
    config.tools = real_backend_tools().await;
    let paths = CliGenerator.generate(&config).unwrap();
    let script = paths
        .iter()
        .find(|path| path.file_name().unwrap() == "alpha")
        .unwrap();

    let output = Command::new(script)
        .args(["echo", "--message", "hello"])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert_eq!(
        String::from_utf8_lossy(&output.stdout).trim(),
        "alpha:hello"
    );
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn generated_cli_script_reports_stopped_proxy_without_traceback() {
    let tempdir = tempfile::tempdir().unwrap();
    let (config, proxy) = running_proxy_config(tempdir.path()).await;
    let mut config = config;
    config.tools = real_backend_tools().await;
    let paths = CliGenerator.generate(&config).unwrap();
    let script = paths
        .iter()
        .find(|path| path.file_name().unwrap() == "alpha")
        .unwrap();

    drop(proxy);

    let output = Command::new(script)
        .args(["echo", "--message", "hello"])
        .output()
        .unwrap();

    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("mcp-compressor proxy is not running"),
        "stderr: {stderr}"
    );
    assert!(!stderr.contains("Traceback"), "stderr: {stderr}");
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn generated_python_module_invokes_real_proxy_and_backend() {
    let tempdir = tempfile::tempdir().unwrap();
    let (config, _proxy) = running_proxy_config(tempdir.path()).await;
    PythonGenerator.generate(&config).unwrap();

    let code = "import alpha; print(alpha.echo('hello'))";
    let output = Command::new(common::python_command())
        .env("PYTHONPATH", tempdir.path())
        .args(["-c", code])
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert_eq!(
        String::from_utf8_lossy(&output.stdout).trim(),
        "alpha:hello"
    );
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn generated_python_module_reports_stopped_proxy_without_urllib_traceback() {
    let tempdir = tempfile::tempdir().unwrap();
    let (config, proxy) = running_proxy_config(tempdir.path()).await;
    let mut config = config;
    config.tools = real_backend_tools().await;
    let paths = PythonGenerator.generate(&config).unwrap();
    let module = paths
        .iter()
        .find(|path| path.file_name().unwrap() == "alpha.py")
        .unwrap();

    drop(proxy);

    let output = Command::new(common::python_command())
        .arg("-c")
        .arg(format!(
            "import sys; sys.path.insert(0, {dir:?}); import alpha; alpha.echo('hello')",
            dir = module.parent().unwrap().display().to_string()
        ))
        .output()
        .unwrap();

    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("mcp-compressor proxy is not running"),
        "stderr: {stderr}"
    );
    assert!(!stderr.contains("urllib.request"), "stderr: {stderr}");
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn generated_typescript_module_invokes_real_proxy_and_backend() {
    let tempdir = tempfile::tempdir().unwrap();
    let (config, _proxy) = running_proxy_config(tempdir.path()).await;
    TypeScriptGenerator.generate(&config).unwrap();

    let module_path = tempdir.path().join("alpha.ts");
    let output = Command::new("bun")
        .arg("--eval")
        .arg(format!(
            "import {{ echo }} from '{}'; console.log(await echo('hello'));",
            module_path.display()
        ))
        .output()
        .unwrap();

    assert!(
        output.status.success(),
        "stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    assert_eq!(
        String::from_utf8_lossy(&output.stdout).trim(),
        "alpha:hello"
    );
}