rustfs-mcp 0.0.6

RustFS MCP (Model Context Protocol) Server
Documentation
// Copyright 2024 RustFS Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use anyhow::{Context, Result};
use clap::Parser;
use rmcp::ServiceExt;
use rustfs_mcp::{Config, RustfsMcpServer};
use std::env;
use tokio::io::{stdin, stdout};
use tracing::{Level, error, info};
use tracing_subscriber::{EnvFilter, FmtSubscriber};

#[tokio::main]
async fn main() -> Result<()> {
    let config = Config::parse();

    init_tracing(&config)?;

    info!("Starting RustFS MCP Server v{}", env!("CARGO_PKG_VERSION"));

    if let Err(e) = config.validate() {
        error!("Configuration validation failed: {}", e);
        print_usage_help();
        std::process::exit(1);
    }

    config.log_configuration();

    if let Err(e) = run_server(config).await {
        error!("Server error: {}", e);
        std::process::exit(1);
    }

    info!("RustFS MCP Server shutdown complete");
    Ok(())
}

async fn run_server(config: Config) -> Result<()> {
    info!("Initializing RustFS MCP Server");

    let server = RustfsMcpServer::new(config).await?;

    info!("Starting MCP server with stdio transport");

    server
        .serve((stdin(), stdout()))
        .await
        .context("Failed to serve MCP server")?
        .waiting()
        .await
        .context("Error while waiting for server shutdown")?;

    Ok(())
}

fn init_tracing(config: &Config) -> Result<()> {
    let filter = EnvFilter::try_from_default_env()
        .or_else(|_| EnvFilter::try_new(&config.log_level))
        .context("Failed to create log filter")?;

    let subscriber = FmtSubscriber::builder()
        .with_max_level(Level::TRACE)
        .with_env_filter(filter)
        .with_target(false)
        .with_thread_ids(false)
        .with_thread_names(false)
        .with_writer(std::io::stderr) // Force logs to stderr to avoid interfering with MCP protocol on stdout
        .finish();

    tracing::subscriber::set_global_default(subscriber)
        .context("Failed to set global tracing subscriber")?;

    Ok(())
}

fn print_usage_help() {
    eprintln!();
    eprintln!("RustFS MCP Server - Model Context Protocol server for S3 operations");
    eprintln!();
    eprintln!("For more help, run: rustfs-mcp --help");
    eprintln!();
    eprintln!("QUICK START:");
    eprintln!("  # Using command-line arguments");
    eprintln!("  rustfs-mcp --access-key-id YOUR_KEY --secret-access-key YOUR_SECRET");
    eprintln!();
    eprintln!("  # Using environment variables");
    eprintln!("  export AWS_ACCESS_KEY_ID=YOUR_KEY");
    eprintln!("  export AWS_SECRET_ACCESS_KEY=YOUR_SECRET");
    eprintln!("  rustfs-mcp");
    eprintln!();
    eprintln!("  # For local development with RustFS");
    eprintln!(
        "  rustfs-mcp --access-key-id minioadmin --secret-access-key minioadmin --endpoint-url http://localhost:9000"
    );
    eprintln!();
}