reasonkit-web 0.1.7

High-performance MCP server for browser automation, web capture, and content extraction. Rust-powered CDP client for AI agents.
Documentation
//! ReasonKit Web MCP Server
//!
//! High-performance browser automation and content extraction server.

use axum::{routing::get, Router};
use clap::Parser;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::signal;

use reasonkit_web::handlers::{
    capture::{capture_router, create_capture_buffer, CaptureConfig, CaptureState},
    feed::{build_feed_router, FeedState},
    orchestrator::{orchestrator_router, OrchestratorState},
    status::{status_router, AppState},
};

/// ReasonKit Web MCP Server
#[derive(Parser, Debug)]
#[command(name = "rk-web")]
#[command(author = "ReasonKit Team <team@reasonkit.sh>")]
#[command(version)]
#[command(about = "ReasonKit Web — MCP Server for Browser Automation")]
#[command(long_about = r#"ReasonKit Web — MCP Server for Browser Automation

Part of The Reasoning Engine suite. This component provides high-performance
browser automation and content extraction via the Model Context Protocol (MCP).

CAPABILITIES:
  • Headless Chrome automation via Chrome DevTools Protocol
  • Page capture and content extraction
  • Screenshot and DOM inspection
  • Form interaction and navigation
  • Session management
  • Full orchestration API for integrated services

INTEGRATION:
  This server implements the MCP specification, enabling AI agents
  to interact with web browsers for research and verification tasks.
  Use with reasonkit-core's ProofGuard ThinkTool for web-based
  claim verification and triangulation.

EXAMPLES:
  # Start server on default port
  rk-web

  # Start with custom port and verbose logging
  rk-web --port 3010 --verbose

  # Specify Chrome path (non-standard location)
  rk-web --chrome-path /opt/chrome/chrome

API ENDPOINTS:
  • /health, /status - Server health and status
  • /capture - Content capture endpoint
  • /feed - Server-Sent Events feed
  • /api/v1/* - Orchestrator API for all integrated services

WEBSITE: https://reasonkit.sh
DOCS:    https://reasonkit.sh/docs/integrations/mcp-server
"#)]
struct Args {
    /// Port to listen on
    #[arg(short, long, default_value = "3001")]
    port: u16,

    /// Host to bind to
    #[arg(short = 'H', long, default_value = "127.0.0.1")]
    host: String,

    /// Enable verbose logging
    #[arg(short, long)]
    verbose: bool,

    /// Path to Chrome/Chromium executable
    #[arg(long)]
    chrome_path: Option<String>,

    /// Run in headless mode
    #[arg(long, default_value = "true")]
    headless: bool,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args = Args::parse();

    // Initialize tracing
    let filter = if args.verbose { "debug" } else { "info" };

    tracing_subscriber::fmt().with_env_filter(filter).init();

    tracing::info!(
        "ReasonKit Web MCP Server starting on {}:{}",
        args.host,
        args.port
    );

    // Create shared application state
    let app_state = Arc::new(AppState::new());

    // Create feed state
    let feed_state = Arc::new(FeedState::new(1024));

    // Create capture state
    let (sender, _receiver) = create_capture_buffer(1000);
    let capture_state = Arc::new(CaptureState::new(CaptureConfig::default(), sender));

    // Create orchestrator state
    let orchestrator_state = Arc::new(OrchestratorState::new(Arc::clone(&app_state)));

    // Build the application router
    // Note: Each router manages its own state via .with_state(), so we use
    // a base Router<()> and merge them without a final .with_state() call.
    let app = Router::new()
        .merge(status_router(Arc::clone(&app_state)))
        .merge(build_feed_router(Arc::clone(&feed_state)))
        .merge(capture_router(Arc::clone(&capture_state)))
        .merge(orchestrator_router(Arc::clone(&orchestrator_state)))
        .route("/", get(|| async { "ReasonKit Web MCP Server" }));

    // Create the address to bind to
    let addr = SocketAddr::new(args.host.parse()?, args.port);

    tracing::info!("Listening on http://{}", addr);

    // Create TCP listener and run the server (axum 0.7+ pattern)
    let listener = tokio::net::TcpListener::bind(&addr).await?;
    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal())
        .await?;

    Ok(())
}

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c()
            .await
            .expect("failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }

    tracing::info!("Shutting down gracefully...");
}