turbomcp-proxy 3.5.0

Universal MCP adapter/generator - introspection, proxying, and code generation for any MCP server
//! Generated MCP proxy
//!
//! Server: {{server_name}} v{{server_version}}
//! Generated: {{generation_date}}
//! Frontend: {{frontend_type}}
//! Backend: {{backend_type}}
//!
//! Environment:
//! - `BACKEND_CMD`: the upstream server's command (required)
//! - `BACKEND_ARGS`: its arguments, comma-separated
//! - `BACKEND_WORKING_DIR`: its working directory
{{#if has_http}}
//! - `BIND_ADDR`: where to listen (default `127.0.0.1:3000`)
{{/if}}
{{#if has_websocket}}
//! - `BIND_ADDR`: where to listen (default `127.0.0.1:3000`)
{{/if}}
//! - `RUST_LOG`: log filter (default `info`); logs go to stderr

#[allow(dead_code)]
mod proxy;
#[allow(dead_code)]
mod types;

use turbomcp_client::Client;
use turbomcp_transport::{ChildProcessConfig, ChildProcessTransport, Transport};

use proxy::ProxyRouter;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // stderr, always: with a stdio frontend, stdout is the MCP channel.
    tracing_subscriber::fmt()
        .with_writer(std::io::stderr)
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    tracing::info!("Starting proxy");

    let router = ProxyRouter::connect(connect_backend().await?).await?;
    tracing::info!("Backend initialized");

{{#if has_http}}
    let bind = std::env::var("BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:3000".to_string());
    tracing::info!("Serving Streamable HTTP on http://{bind}/mcp");
    turbomcp_server::transport::http::run(&router, &bind).await?;
{{else}}
{{#if has_websocket}}
    let bind = std::env::var("BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:3000".to_string());
    tracing::info!("Serving WebSocket on ws://{bind}");
    turbomcp_server::transport::websocket::run(&router, &bind).await?;
{{else}}
    turbomcp_server::transport::stdio::run(&router).await?;
{{/if}}
{{/if}}

    Ok(())
}

/// Start the upstream server as a subprocess.
async fn connect_backend() -> Result<Client<ChildProcessTransport>, Box<dyn std::error::Error>> {
    let command = std::env::var("BACKEND_CMD")
        .map_err(|_| "BACKEND_CMD must name the upstream server's command")?;
    let args: Vec<String> = std::env::var("BACKEND_ARGS")
        .unwrap_or_default()
        .split(',')
        .filter(|arg| !arg.is_empty())
        .map(String::from)
        .collect();

    tracing::info!("Backend command: {command} {args:?}");

    let transport = ChildProcessTransport::new(ChildProcessConfig {
        command,
        args,
        working_directory: std::env::var("BACKEND_WORKING_DIR").ok(),
        environment: None,
        ..Default::default()
    });
    transport.connect().await?;

    Ok(Client::new(transport))
}