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},
};
#[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 {
#[arg(short, long, default_value = "3001")]
port: u16,
#[arg(short = 'H', long, default_value = "127.0.0.1")]
host: String,
#[arg(short, long)]
verbose: bool,
#[arg(long)]
chrome_path: Option<String>,
#[arg(long, default_value = "true")]
headless: bool,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
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
);
let app_state = Arc::new(AppState::new());
let feed_state = Arc::new(FeedState::new(1024));
let (sender, _receiver) = create_capture_buffer(1000);
let capture_state = Arc::new(CaptureState::new(CaptureConfig::default(), sender));
let orchestrator_state = Arc::new(OrchestratorState::new(Arc::clone(&app_state)));
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" }));
let addr = SocketAddr::new(args.host.parse()?, args.port);
tracing::info!("Listening on http://{}", addr);
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...");
}